From 2ecaf023b7a4bf1782fade033aa27de43b7002cf Mon Sep 17 00:00:00 2001 From: Jake Nelson Date: Thu, 18 Sep 2025 12:34:41 +1000 Subject: [PATCH] feat: add remaining mode to context % widgets (#83) --- src/widgets/ContextPercentage.ts | 45 +++++++++++++++++++++++--- src/widgets/ContextPercentageUsable.ts | 45 +++++++++++++++++++++++--- 2 files changed, 80 insertions(+), 10 deletions(-) diff --git a/src/widgets/ContextPercentage.ts b/src/widgets/ContextPercentage.ts index 5bf6974..dd2c40f 100644 --- a/src/widgets/ContextPercentage.ts +++ b/src/widgets/ContextPercentage.ts @@ -1,6 +1,7 @@ import type { RenderContext } from '../types/RenderContext'; import type { Settings } from '../types/Settings'; import type { + CustomKeybind, Widget, WidgetEditorDisplay, WidgetItem @@ -8,22 +9,56 @@ import type { export class ContextPercentageWidget implements Widget { getDefaultColor(): string { return 'blue'; } - getDescription(): string { return 'Shows percentage of context window used (of 200k tokens)'; } + getDescription(): string { return 'Shows percentage of context window used or remaining (of 200k tokens)'; } getDisplayName(): string { return 'Context %'; } getEditorDisplay(item: WidgetItem): WidgetEditorDisplay { - return { displayText: this.getDisplayName() }; + const isInverse = item.metadata?.inverse === 'true'; + const modifiers: string[] = []; + + if (isInverse) { + modifiers.push('remaining'); + } + + return { + displayText: this.getDisplayName(), + modifierText: modifiers.length > 0 ? `(${modifiers.join(', ')})` : undefined + }; + } + + handleEditorAction(action: string, item: WidgetItem): WidgetItem | null { + if (action === 'toggle-inverse') { + const currentState = item.metadata?.inverse === 'true'; + return { + ...item, + metadata: { + ...item.metadata, + inverse: (!currentState).toString() + } + }; + } + return null; } render(item: WidgetItem, context: RenderContext, settings: Settings): string | null { + const isInverse = item.metadata?.inverse === 'true'; + if (context.isPreview) { - return item.rawValue ? '9.3%' : 'Ctx: 9.3%'; + const previewValue = isInverse ? '90.7%' : '9.3%'; + return item.rawValue ? previewValue : `Ctx: ${previewValue}`; } else if (context.tokenMetrics) { - const percentage = Math.min(100, (context.tokenMetrics.contextLength / 200000) * 100); - return item.rawValue ? `${percentage.toFixed(1)}%` : `Ctx: ${percentage.toFixed(1)}%`; + const usedPercentage = Math.min(100, (context.tokenMetrics.contextLength / 200000) * 100); + const displayPercentage = isInverse ? (100 - usedPercentage) : usedPercentage; + return item.rawValue ? `${displayPercentage.toFixed(1)}%` : `Ctx: ${displayPercentage.toFixed(1)}%`; } return null; } + getCustomKeybinds(): CustomKeybind[] { + return [ + { key: 'l', label: '(l)eft/remaining', action: 'toggle-inverse' } + ]; + } + supportsRawValue(): boolean { return true; } supportsColors(item: WidgetItem): boolean { return true; } } \ No newline at end of file diff --git a/src/widgets/ContextPercentageUsable.ts b/src/widgets/ContextPercentageUsable.ts index 37bd1c8..874e1af 100644 --- a/src/widgets/ContextPercentageUsable.ts +++ b/src/widgets/ContextPercentageUsable.ts @@ -1,6 +1,7 @@ import type { RenderContext } from '../types/RenderContext'; import type { Settings } from '../types/Settings'; import type { + CustomKeybind, Widget, WidgetEditorDisplay, WidgetItem @@ -8,22 +9,56 @@ import type { export class ContextPercentageUsableWidget implements Widget { getDefaultColor(): string { return 'green'; } - getDescription(): string { return 'Shows percentage of usable context window used (of 160k tokens before auto-compact)'; } + getDescription(): string { return 'Shows percentage of usable context window used or remaining (of 160k tokens before auto-compact)'; } getDisplayName(): string { return 'Context % (usable)'; } getEditorDisplay(item: WidgetItem): WidgetEditorDisplay { - return { displayText: this.getDisplayName() }; + const isInverse = item.metadata?.inverse === 'true'; + const modifiers: string[] = []; + + if (isInverse) { + modifiers.push('remaining'); + } + + return { + displayText: this.getDisplayName(), + modifierText: modifiers.length > 0 ? `(${modifiers.join(', ')})` : undefined + }; + } + + handleEditorAction(action: string, item: WidgetItem): WidgetItem | null { + if (action === 'toggle-inverse') { + const currentState = item.metadata?.inverse === 'true'; + return { + ...item, + metadata: { + ...item.metadata, + inverse: (!currentState).toString() + } + }; + } + return null; } render(item: WidgetItem, context: RenderContext, settings: Settings): string | null { + const isInverse = item.metadata?.inverse === 'true'; + if (context.isPreview) { - return item.rawValue ? '11.6%' : 'Ctx(u): 11.6%'; + const previewValue = isInverse ? '88.4%' : '11.6%'; + return item.rawValue ? previewValue : `Ctx(u): ${previewValue}`; } else if (context.tokenMetrics) { - const percentage = Math.min(100, (context.tokenMetrics.contextLength / 160000) * 100); - return item.rawValue ? `${percentage.toFixed(1)}%` : `Ctx(u): ${percentage.toFixed(1)}%`; + const usedPercentage = Math.min(100, (context.tokenMetrics.contextLength / 160000) * 100); + const displayPercentage = isInverse ? (100 - usedPercentage) : usedPercentage; + return item.rawValue ? `${displayPercentage.toFixed(1)}%` : `Ctx(u): ${displayPercentage.toFixed(1)}%`; } return null; } + getCustomKeybinds(): CustomKeybind[] { + return [ + { key: 'l', label: '(l)eft/remaining', action: 'toggle-inverse' } + ]; + } + supportsRawValue(): boolean { return true; } supportsColors(item: WidgetItem): boolean { return true; } } \ No newline at end of file