From 7e4f5ef4209358f54d64be762518ca5079952799 Mon Sep 17 00:00:00 2001 From: Zach Date: Wed, 5 Aug 2026 20:11:59 -0500 Subject: [PATCH] refactor(widgets): collapse the duplicated Nerd Font toggle helpers VimMode, SandboxStatus, RemoteControlStatus, VoiceStatus and CompactionCounter each carried their own copy of the same four helpers. The first four were byte-identical apart from the format type name; CompactionCounter's differed only by inlining its predicate. Net -163 lines. shared/metadata.ts gains isNerdFontEnabled, setNerdFontFormat and toggleNerdFont, each taking a NerdFontFormats config as a trailing argument, which matches how symbol-override.tsx threads SymbolSlot. Each widget keeps only its own canUseNerdFont predicate, the part that is genuinely per-widget. CompactionCounter gains one as a named function, which also covers the copy inlined in its keybind gate. toggleMetadataFlag is deliberately not reused here: it writes nerdFont:'false', while every one of these widgets deletes the key and collapses empty metadata to undefined. SandboxStatus and VoiceStatus also shared a byte-identical config cwd resolver; both now call resolveClaudeConfigCwd next to the settings layering it feeds. --- src/utils/claude-settings.ts | 17 ++++++ src/widgets/CompactionCounter.ts | 76 ++++++------------------ src/widgets/RemoteControlStatus.ts | 75 +++++------------------- src/widgets/SandboxStatus.ts | 92 +++++++----------------------- src/widgets/VimMode.ts | 75 +++++------------------- src/widgets/VoiceStatus.ts | 92 +++++++----------------------- src/widgets/shared/metadata.ts | 56 ++++++++++++++++++ 7 files changed, 160 insertions(+), 323 deletions(-) diff --git a/src/utils/claude-settings.ts b/src/utils/claude-settings.ts index 8aaecfce0..6b9ea69c1 100644 --- a/src/utils/claude-settings.ts +++ b/src/utils/claude-settings.ts @@ -5,6 +5,7 @@ import * as path from 'path'; import { z } from 'zod'; import type { ClaudeSettings } from '../types/ClaudeSettings'; +import type { RenderContext } from '../types/RenderContext'; import { SettingsSchema, type InstallationMetadata, @@ -518,6 +519,22 @@ function getLayeredSettingsCandidatePathsByPriority(cwd: string): string[] { return Array.from(new Set(candidates)); } +/** + * Picks the directory whose `.claude` layer `getVoiceConfig` and `getSandboxConfig` read. + * + * The project directory is tried first because that is where the project settings + * layers live; the session cwd can sit anywhere beneath it. + */ +export function resolveClaudeConfigCwd(context: RenderContext): string | undefined { + const candidates = [ + context.data?.workspace?.project_dir, + context.data?.cwd, + context.data?.workspace?.current_dir + ]; + + return candidates.find(candidate => typeof candidate === 'string' && candidate.trim().length > 0); +} + interface VoiceLayerResult { fileExisted: boolean; enabled: boolean | undefined; diff --git a/src/widgets/CompactionCounter.ts b/src/widgets/CompactionCounter.ts index 6560aa9cb..7b2a0212d 100644 --- a/src/widgets/CompactionCounter.ts +++ b/src/widgets/CompactionCounter.ts @@ -15,7 +15,11 @@ import { formatTokens } from '../utils/format-tokens'; import { isMetadataFlagEnabled, - toggleMetadataFlag + isNerdFontEnabled, + setNerdFontFormat, + toggleMetadataFlag, + toggleNerdFont, + type NerdFontFormats } from './shared/metadata'; import { getSlotSymbol, @@ -34,7 +38,6 @@ const CYCLE_FORMAT_ACTION = 'cycle-format'; const TOGGLE_HIDE_ZERO_ACTION = 'toggle-hide-zero'; const TOGGLE_NERD_FONT_ACTION = 'toggle-nerd-font'; const HIDE_ZERO_METADATA_KEY = 'hideZero'; -const NERD_FONT_METADATA_KEY = 'nerdFont'; const TOGGLE_TRIGGERS_ACTION = 'toggle-triggers'; const SHOW_TRIGGERS_METADATA_KEY = 'showTriggers'; const TOGGLE_RECLAIMED_ACTION = 'toggle-reclaimed'; @@ -60,42 +63,15 @@ function getFormat(item: WidgetItem): CompactionCounterFormat { return (FORMATS as readonly string[]).includes(format ?? '') ? (format as CompactionCounterFormat) : DEFAULT_FORMAT; } -function removeNerdFont(item: WidgetItem): WidgetItem { - const { [NERD_FONT_METADATA_KEY]: removedNerdFont, ...restMetadata } = item.metadata ?? {}; - void removedNerdFont; - - return { - ...item, - metadata: Object.keys(restMetadata).length > 0 ? restMetadata : undefined - }; -} - -function setFormat(item: WidgetItem, format: CompactionCounterFormat): WidgetItem { - if (format === DEFAULT_FORMAT) { - const { format: removedFormat, ...restMetadata } = item.metadata ?? {}; - void removedFormat; - - return { - ...item, - metadata: Object.keys(restMetadata).length > 0 ? restMetadata : undefined - }; - } - - const { [NERD_FONT_METADATA_KEY]: removedNerdFont, ...restMetadata } = item.metadata ?? {}; - void removedNerdFont; - - return { - ...item, - metadata: { - ...restMetadata, - format - } - }; +// Only the icon format draws a glyph; the other two are text. +function canUseNerdFont(item: WidgetItem): boolean { + return getFormat(item) === DEFAULT_FORMAT; } -function isNerdFontEnabled(item: WidgetItem): boolean { - return item.metadata?.[NERD_FONT_METADATA_KEY] === 'true' && getFormat(item) === DEFAULT_FORMAT; -} +const NERD_FONT_FORMATS: NerdFontFormats = { + defaultFormat: DEFAULT_FORMAT, + canUseNerdFont +}; function isHideZeroEnabled(item: WidgetItem): boolean { return item.metadata?.[HIDE_ZERO_METADATA_KEY] === 'true'; @@ -179,24 +155,6 @@ function formatStats(data: CompactionData, item: WidgetItem, icon: string): stri return out; } -function toggleNerdFont(item: WidgetItem): WidgetItem { - if (getFormat(item) !== DEFAULT_FORMAT) { - return removeNerdFont(item); - } - - if (!isNerdFontEnabled(item)) { - return { - ...item, - metadata: { - ...(item.metadata ?? {}), - [NERD_FONT_METADATA_KEY]: 'true' - } - }; - } - - return removeNerdFont(item); -} - function formatCount(count: number, format: CompactionCounterFormat, icon: string): string { switch (format) { case 'icon-space-number': return `${icon} ${count}`; @@ -230,7 +188,7 @@ export class CompactionCounterWidget implements Widget { modifiers.push(`${metric} value`); } else { modifiers.push(getFormat(item)); - if (isNerdFontEnabled(item)) { + if (isNerdFontEnabled(item, NERD_FONT_FORMATS)) { modifiers.push('nerd font'); } if (isMetadataFlagEnabled(item, SHOW_TRIGGERS_METADATA_KEY)) { @@ -262,7 +220,7 @@ export class CompactionCounterWidget implements Widget { const currentFormat = getFormat(item); const nextFormat = FORMATS[(FORMATS.indexOf(currentFormat) + 1) % FORMATS.length] ?? DEFAULT_FORMAT; - return setFormat(item, nextFormat); + return setNerdFontFormat(item, nextFormat, NERD_FONT_FORMATS); } if (action === TOGGLE_HIDE_ZERO_ACTION) { @@ -270,7 +228,7 @@ export class CompactionCounterWidget implements Widget { } if (action === TOGGLE_NERD_FONT_ACTION) { - return toggleNerdFont(item); + return toggleNerdFont(item, NERD_FONT_FORMATS); } if (action === TOGGLE_TRIGGERS_ACTION) { @@ -301,7 +259,7 @@ export class CompactionCounterWidget implements Widget { return null; } - const icon = isNerdFontEnabled(item) ? COMPACTION_NERD_FONT_ICON : COMPACTION_ICON; + const icon = isNerdFontEnabled(item, NERD_FONT_FORMATS) ? COMPACTION_NERD_FONT_ICON : COMPACTION_ICON; return formatStats(data, item, icon); } @@ -318,7 +276,7 @@ export class CompactionCounterWidget implements Widget { } keybinds.push({ key: 'f', label: '(f)ormat', action: CYCLE_FORMAT_ACTION }); - if (item === undefined || getFormat(item) === DEFAULT_FORMAT) { + if (item === undefined || canUseNerdFont(item)) { keybinds.push({ key: 'n', label: '(n)erd font', action: TOGGLE_NERD_FONT_ACTION }); } keybinds.push({ key: 's', label: '(s)plit by trigger', action: TOGGLE_TRIGGERS_ACTION }); diff --git a/src/widgets/RemoteControlStatus.ts b/src/widgets/RemoteControlStatus.ts index 344e794b1..87ceec019 100644 --- a/src/widgets/RemoteControlStatus.ts +++ b/src/widgets/RemoteControlStatus.ts @@ -8,6 +8,13 @@ import type { } from '../types/Widget'; import { getRemoteControlStatus } from '../utils/claude-settings'; +import { + isNerdFontEnabled, + setNerdFontFormat, + toggleNerdFont, + type NerdFontFormats +} from './shared/metadata'; + const SATELLITE_EMOJI = '📡'; const SATELLITE_NERD_FONT = ''; const SATELLITE_SLASH_NERD_FONT = ''; @@ -24,7 +31,6 @@ type RemoteFormat = typeof FORMATS[number]; const DEFAULT_FORMAT: RemoteFormat = 'icon'; const CYCLE_FORMAT_ACTION = 'cycle-format'; const TOGGLE_NERD_FONT_ACTION = 'toggle-nerd-font'; -const NERD_FONT_METADATA_KEY = 'nerdFont'; function getFormat(item: WidgetItem): RemoteFormat { const f = item.metadata?.format; @@ -36,61 +42,10 @@ function canUseNerdFont(item: WidgetItem): boolean { return format === 'icon' || (format === 'icon-text' && !item.rawValue); } -function removeNerdFont(item: WidgetItem): WidgetItem { - const { [NERD_FONT_METADATA_KEY]: removedNerdFont, ...restMetadata } = item.metadata ?? {}; - void removedNerdFont; - - return { - ...item, - metadata: Object.keys(restMetadata).length > 0 ? restMetadata : undefined - }; -} - -function setFormat(item: WidgetItem, format: RemoteFormat): WidgetItem { - let updatedItem: WidgetItem; - - if (format === DEFAULT_FORMAT) { - const { format: removedFormat, ...restMetadata } = item.metadata ?? {}; - void removedFormat; - - updatedItem = { - ...item, - metadata: Object.keys(restMetadata).length > 0 ? restMetadata : undefined - }; - } else { - updatedItem = { - ...item, - metadata: { - ...(item.metadata ?? {}), - format - } - }; - } - - return canUseNerdFont(updatedItem) ? updatedItem : removeNerdFont(updatedItem); -} - -function isNerdFontEnabled(item: WidgetItem): boolean { - return canUseNerdFont(item) && item.metadata?.[NERD_FONT_METADATA_KEY] === 'true'; -} - -function toggleNerdFont(item: WidgetItem): WidgetItem { - if (!canUseNerdFont(item)) { - return removeNerdFont(item); - } - - if (!isNerdFontEnabled(item)) { - return { - ...item, - metadata: { - ...(item.metadata ?? {}), - [NERD_FONT_METADATA_KEY]: 'true' - } - }; - } - - return removeNerdFont(item); -} +const NERD_FONT_FORMATS: NerdFontFormats = { + defaultFormat: DEFAULT_FORMAT, + canUseNerdFont +}; function formatStatus(enabled: boolean, format: RemoteFormat, nerdFont: boolean, rawValue: boolean): string { const stateText = enabled ? 'on' : 'off'; @@ -123,7 +78,7 @@ export class RemoteControlStatusWidget implements Widget { getEditorDisplay(item: WidgetItem): WidgetEditorDisplay { const modifiers: string[] = [getFormat(item)]; - if (isNerdFontEnabled(item)) { + if (isNerdFontEnabled(item, NERD_FONT_FORMATS)) { modifiers.push('nerd font'); } @@ -138,11 +93,11 @@ export class RemoteControlStatusWidget implements Widget { const currentFormat = getFormat(item); const nextFormat = FORMATS[(FORMATS.indexOf(currentFormat) + 1) % FORMATS.length] ?? DEFAULT_FORMAT; - return setFormat(item, nextFormat); + return setNerdFontFormat(item, nextFormat, NERD_FONT_FORMATS); } if (action === TOGGLE_NERD_FONT_ACTION) { - return toggleNerdFont(item); + return toggleNerdFont(item, NERD_FONT_FORMATS); } return null; @@ -150,7 +105,7 @@ export class RemoteControlStatusWidget implements Widget { render(item: WidgetItem, context: RenderContext, _settings: Settings): string | null { const format = getFormat(item); - const nerdFont = isNerdFontEnabled(item); + const nerdFont = isNerdFontEnabled(item, NERD_FONT_FORMATS); if (context.isPreview) { return formatStatus(true, format, nerdFont, item.rawValue ?? false); diff --git a/src/widgets/SandboxStatus.ts b/src/widgets/SandboxStatus.ts index c6fdda68a..54f65b1ac 100644 --- a/src/widgets/SandboxStatus.ts +++ b/src/widgets/SandboxStatus.ts @@ -6,7 +6,17 @@ import type { WidgetEditorDisplay, WidgetItem } from '../types/Widget'; -import { getSandboxConfig } from '../utils/claude-settings'; +import { + getSandboxConfig, + resolveClaudeConfigCwd +} from '../utils/claude-settings'; + +import { + isNerdFontEnabled, + setNerdFontFormat, + toggleNerdFont, + type NerdFontFormats +} from './shared/metadata'; const DOT_ON = '●'; const DOT_OFF = '○'; @@ -19,7 +29,6 @@ type SandboxFormat = typeof FORMATS[number]; const DEFAULT_FORMAT: SandboxFormat = 'glyph'; const CYCLE_FORMAT_ACTION = 'cycle-format'; const TOGGLE_NERD_FONT_ACTION = 'toggle-nerd-font'; -const NERD_FONT_METADATA_KEY = 'nerdFont'; function getFormat(item: WidgetItem): SandboxFormat { const f = item.metadata?.format; @@ -30,61 +39,10 @@ function canUseNerdFont(item: WidgetItem): boolean { return getFormat(item) === 'glyph'; } -function removeNerdFont(item: WidgetItem): WidgetItem { - const { [NERD_FONT_METADATA_KEY]: removedNerdFont, ...restMetadata } = item.metadata ?? {}; - void removedNerdFont; - - return { - ...item, - metadata: Object.keys(restMetadata).length > 0 ? restMetadata : undefined - }; -} - -function setFormat(item: WidgetItem, format: SandboxFormat): WidgetItem { - let updatedItem: WidgetItem; - - if (format === DEFAULT_FORMAT) { - const { format: removedFormat, ...restMetadata } = item.metadata ?? {}; - void removedFormat; - - updatedItem = { - ...item, - metadata: Object.keys(restMetadata).length > 0 ? restMetadata : undefined - }; - } else { - updatedItem = { - ...item, - metadata: { - ...(item.metadata ?? {}), - format - } - }; - } - - return canUseNerdFont(updatedItem) ? updatedItem : removeNerdFont(updatedItem); -} - -function isNerdFontEnabled(item: WidgetItem): boolean { - return canUseNerdFont(item) && item.metadata?.[NERD_FONT_METADATA_KEY] === 'true'; -} - -function toggleNerdFont(item: WidgetItem): WidgetItem { - if (!canUseNerdFont(item)) { - return removeNerdFont(item); - } - - if (!isNerdFontEnabled(item)) { - return { - ...item, - metadata: { - ...(item.metadata ?? {}), - [NERD_FONT_METADATA_KEY]: 'true' - } - }; - } - - return removeNerdFont(item); -} +const NERD_FONT_FORMATS: NerdFontFormats = { + defaultFormat: DEFAULT_FORMAT, + canUseNerdFont +}; function formatStatus(enabled: boolean, format: SandboxFormat, nerdFont: boolean, rawValue: boolean): string { const stateText = enabled ? 'ON' : 'OFF'; @@ -102,16 +60,6 @@ function formatStatus(enabled: boolean, format: SandboxFormat, nerdFont: boolean } } -function resolveSandboxConfigCwd(context: RenderContext): string | undefined { - const candidates = [ - context.data?.workspace?.project_dir, - context.data?.cwd, - context.data?.workspace?.current_dir - ]; - - return candidates.find(candidate => typeof candidate === 'string' && candidate.trim().length > 0); -} - export class SandboxStatusWidget implements Widget { getDefaultColor(): string { return 'green'; } getDescription(): string { @@ -126,7 +74,7 @@ export class SandboxStatusWidget implements Widget { getEditorDisplay(item: WidgetItem): WidgetEditorDisplay { const modifiers: string[] = [getFormat(item)]; - if (isNerdFontEnabled(item)) { + if (isNerdFontEnabled(item, NERD_FONT_FORMATS)) { modifiers.push('nerd font'); } @@ -141,11 +89,11 @@ export class SandboxStatusWidget implements Widget { const currentFormat = getFormat(item); const nextFormat = FORMATS[(FORMATS.indexOf(currentFormat) + 1) % FORMATS.length] ?? DEFAULT_FORMAT; - return setFormat(item, nextFormat); + return setNerdFontFormat(item, nextFormat, NERD_FONT_FORMATS); } if (action === TOGGLE_NERD_FONT_ACTION) { - return toggleNerdFont(item); + return toggleNerdFont(item, NERD_FONT_FORMATS); } return null; @@ -153,13 +101,13 @@ export class SandboxStatusWidget implements Widget { render(item: WidgetItem, context: RenderContext, _settings: Settings): string | null { const format = getFormat(item); - const nerdFont = isNerdFontEnabled(item); + const nerdFont = isNerdFontEnabled(item, NERD_FONT_FORMATS); if (context.isPreview) { return formatStatus(true, format, nerdFont, item.rawValue ?? false); } - const config = getSandboxConfig(resolveSandboxConfigCwd(context)); + const config = getSandboxConfig(resolveClaudeConfigCwd(context)); if (config === null) { return null; } diff --git a/src/widgets/VimMode.ts b/src/widgets/VimMode.ts index b2fb471f4..1847dcda0 100644 --- a/src/widgets/VimMode.ts +++ b/src/widgets/VimMode.ts @@ -7,6 +7,13 @@ import type { WidgetItem } from '../types/Widget'; +import { + isNerdFontEnabled, + setNerdFontFormat, + toggleNerdFont, + type NerdFontFormats +} from './shared/metadata'; + const VIM_ICON = 'v'; const VIM_NERD_FONT_ICON = '\uE62B'; @@ -16,7 +23,6 @@ type VimFormat = typeof FORMATS[number]; const DEFAULT_FORMAT: VimFormat = 'icon-dash-letter'; const CYCLE_FORMAT_ACTION = 'cycle-format'; const TOGGLE_NERD_FONT_ACTION = 'toggle-nerd-font'; -const NERD_FONT_METADATA_KEY = 'nerdFont'; function getFormat(item: WidgetItem): VimFormat { const f = item.metadata?.format; @@ -28,61 +34,10 @@ function canUseNerdFont(item: WidgetItem): boolean { return format === 'icon-dash-letter' || format === 'icon-letter' || format === 'icon'; } -function removeNerdFont(item: WidgetItem): WidgetItem { - const { [NERD_FONT_METADATA_KEY]: removedNerdFont, ...restMetadata } = item.metadata ?? {}; - void removedNerdFont; - - return { - ...item, - metadata: Object.keys(restMetadata).length > 0 ? restMetadata : undefined - }; -} - -function setFormat(item: WidgetItem, format: VimFormat): WidgetItem { - let updatedItem: WidgetItem; - - if (format === DEFAULT_FORMAT) { - const { format: removedFormat, ...restMetadata } = item.metadata ?? {}; - void removedFormat; - - updatedItem = { - ...item, - metadata: Object.keys(restMetadata).length > 0 ? restMetadata : undefined - }; - } else { - updatedItem = { - ...item, - metadata: { - ...(item.metadata ?? {}), - format - } - }; - } - - return canUseNerdFont(updatedItem) ? updatedItem : removeNerdFont(updatedItem); -} - -function isNerdFontEnabled(item: WidgetItem): boolean { - return canUseNerdFont(item) && item.metadata?.[NERD_FONT_METADATA_KEY] === 'true'; -} - -function toggleNerdFont(item: WidgetItem): WidgetItem { - if (!canUseNerdFont(item)) { - return removeNerdFont(item); - } - - if (!isNerdFontEnabled(item)) { - return { - ...item, - metadata: { - ...(item.metadata ?? {}), - [NERD_FONT_METADATA_KEY]: 'true' - } - }; - } - - return removeNerdFont(item); -} +const NERD_FONT_FORMATS: NerdFontFormats = { + defaultFormat: DEFAULT_FORMAT, + canUseNerdFont +}; function formatMode(mode: string, format: VimFormat, icon: string): string { const letter = mode === 'NORMAL' ? 'N' : mode === 'INSERT' ? 'I' : (mode[0] ?? mode); @@ -103,7 +58,7 @@ export class VimModeWidget implements Widget { getEditorDisplay(item: WidgetItem): WidgetEditorDisplay { const modifiers: string[] = [getFormat(item)]; - if (isNerdFontEnabled(item)) { + if (isNerdFontEnabled(item, NERD_FONT_FORMATS)) { modifiers.push('nerd font'); } @@ -118,11 +73,11 @@ export class VimModeWidget implements Widget { const currentFormat = getFormat(item); const nextFormat = FORMATS[(FORMATS.indexOf(currentFormat) + 1) % FORMATS.length] ?? DEFAULT_FORMAT; - return setFormat(item, nextFormat); + return setNerdFontFormat(item, nextFormat, NERD_FONT_FORMATS); } if (action === TOGGLE_NERD_FONT_ACTION) { - return toggleNerdFont(item); + return toggleNerdFont(item, NERD_FONT_FORMATS); } return null; @@ -130,7 +85,7 @@ export class VimModeWidget implements Widget { render(item: WidgetItem, context: RenderContext, _settings: Settings): string | null { const format = getFormat(item); - const icon = isNerdFontEnabled(item) ? VIM_NERD_FONT_ICON : VIM_ICON; + const icon = isNerdFontEnabled(item, NERD_FONT_FORMATS) ? VIM_NERD_FONT_ICON : VIM_ICON; if (context.isPreview) return formatMode('NORMAL', format, icon); diff --git a/src/widgets/VoiceStatus.ts b/src/widgets/VoiceStatus.ts index 92117f9d9..a92f96a3a 100644 --- a/src/widgets/VoiceStatus.ts +++ b/src/widgets/VoiceStatus.ts @@ -6,7 +6,17 @@ import type { WidgetEditorDisplay, WidgetItem } from '../types/Widget'; -import { getVoiceConfig } from '../utils/claude-settings'; +import { + getVoiceConfig, + resolveClaudeConfigCwd +} from '../utils/claude-settings'; + +import { + isNerdFontEnabled, + setNerdFontFormat, + toggleNerdFont, + type NerdFontFormats +} from './shared/metadata'; const MIC_EMOJI = '🎤'; const MIC_NERD_FONT = ''; @@ -20,7 +30,6 @@ type VoiceFormat = typeof FORMATS[number]; const DEFAULT_FORMAT: VoiceFormat = 'icon'; const CYCLE_FORMAT_ACTION = 'cycle-format'; const TOGGLE_NERD_FONT_ACTION = 'toggle-nerd-font'; -const NERD_FONT_METADATA_KEY = 'nerdFont'; function getFormat(item: WidgetItem): VoiceFormat { const f = item.metadata?.format; @@ -32,61 +41,10 @@ function canUseNerdFont(item: WidgetItem): boolean { return format === 'icon' || (format === 'icon-text' && !item.rawValue); } -function removeNerdFont(item: WidgetItem): WidgetItem { - const { [NERD_FONT_METADATA_KEY]: removedNerdFont, ...restMetadata } = item.metadata ?? {}; - void removedNerdFont; - - return { - ...item, - metadata: Object.keys(restMetadata).length > 0 ? restMetadata : undefined - }; -} - -function setFormat(item: WidgetItem, format: VoiceFormat): WidgetItem { - let updatedItem: WidgetItem; - - if (format === DEFAULT_FORMAT) { - const { format: removedFormat, ...restMetadata } = item.metadata ?? {}; - void removedFormat; - - updatedItem = { - ...item, - metadata: Object.keys(restMetadata).length > 0 ? restMetadata : undefined - }; - } else { - updatedItem = { - ...item, - metadata: { - ...(item.metadata ?? {}), - format - } - }; - } - - return canUseNerdFont(updatedItem) ? updatedItem : removeNerdFont(updatedItem); -} - -function isNerdFontEnabled(item: WidgetItem): boolean { - return canUseNerdFont(item) && item.metadata?.[NERD_FONT_METADATA_KEY] === 'true'; -} - -function toggleNerdFont(item: WidgetItem): WidgetItem { - if (!canUseNerdFont(item)) { - return removeNerdFont(item); - } - - if (!isNerdFontEnabled(item)) { - return { - ...item, - metadata: { - ...(item.metadata ?? {}), - [NERD_FONT_METADATA_KEY]: 'true' - } - }; - } - - return removeNerdFont(item); -} +const NERD_FONT_FORMATS: NerdFontFormats = { + defaultFormat: DEFAULT_FORMAT, + canUseNerdFont +}; function formatStatus(enabled: boolean, format: VoiceFormat, nerdFont: boolean, rawValue: boolean): string { const stateText = enabled ? 'on' : 'off'; @@ -107,16 +65,6 @@ function formatStatus(enabled: boolean, format: VoiceFormat, nerdFont: boolean, } } -function resolveVoiceConfigCwd(context: RenderContext): string | undefined { - const candidates = [ - context.data?.workspace?.project_dir, - context.data?.cwd, - context.data?.workspace?.current_dir - ]; - - return candidates.find(candidate => typeof candidate === 'string' && candidate.trim().length > 0); -} - export class VoiceStatusWidget implements Widget { getDefaultColor(): string { return 'magenta'; } getDescription(): string { return 'Shows whether Claude Code voice input is enabled'; } @@ -125,7 +73,7 @@ export class VoiceStatusWidget implements Widget { getEditorDisplay(item: WidgetItem): WidgetEditorDisplay { const modifiers: string[] = [getFormat(item)]; - if (isNerdFontEnabled(item)) { + if (isNerdFontEnabled(item, NERD_FONT_FORMATS)) { modifiers.push('nerd font'); } @@ -140,11 +88,11 @@ export class VoiceStatusWidget implements Widget { const currentFormat = getFormat(item); const nextFormat = FORMATS[(FORMATS.indexOf(currentFormat) + 1) % FORMATS.length] ?? DEFAULT_FORMAT; - return setFormat(item, nextFormat); + return setNerdFontFormat(item, nextFormat, NERD_FONT_FORMATS); } if (action === TOGGLE_NERD_FONT_ACTION) { - return toggleNerdFont(item); + return toggleNerdFont(item, NERD_FONT_FORMATS); } return null; @@ -152,13 +100,13 @@ export class VoiceStatusWidget implements Widget { render(item: WidgetItem, context: RenderContext, _settings: Settings): string | null { const format = getFormat(item); - const nerdFont = isNerdFontEnabled(item); + const nerdFont = isNerdFontEnabled(item, NERD_FONT_FORMATS); if (context.isPreview) { return formatStatus(true, format, nerdFont, item.rawValue ?? false); } - const config = getVoiceConfig(resolveVoiceConfigCwd(context)); + const config = getVoiceConfig(resolveClaudeConfigCwd(context)); if (config === null) { return null; } diff --git a/src/widgets/shared/metadata.ts b/src/widgets/shared/metadata.ts index 98293c762..bd7936910 100644 --- a/src/widgets/shared/metadata.ts +++ b/src/widgets/shared/metadata.ts @@ -1,5 +1,8 @@ import type { WidgetItem } from '../../types/Widget'; +const FORMAT_METADATA_KEY = 'format'; +const NERD_FONT_METADATA_KEY = 'nerdFont'; + export function isMetadataFlagEnabled(item: WidgetItem, key: string): boolean { return item.metadata?.[key] === 'true'; } @@ -24,3 +27,56 @@ export function removeMetadataKeys(item: WidgetItem, keys: string[]): WidgetItem metadata: Object.keys(nextMetadata).length > 0 ? nextMetadata : undefined }; } + +// The per-widget half of the Nerd Font toggle. Only some formats draw a glyph +// the toggle can swap, and the predicate takes the whole item because raw value +// mode can strip the glyph from a format that otherwise carries one. +export interface NerdFontFormats { + defaultFormat: TFormat; + canUseNerdFont: (item: WidgetItem) => boolean; +} + +// Off is the absence of the key, not 'false', so an item left at its defaults +// stores no metadata at all. +function removeNerdFont(item: WidgetItem): WidgetItem { + return removeMetadataKeys(item, [NERD_FONT_METADATA_KEY]); +} + +export function isNerdFontEnabled(item: WidgetItem, formats: NerdFontFormats): boolean { + return formats.canUseNerdFont(item) && isMetadataFlagEnabled(item, NERD_FONT_METADATA_KEY); +} + +/** Writes the widget's format, clearing the Nerd Font flag when the new format has no glyph. */ +export function setNerdFontFormat(item: WidgetItem, format: TFormat, formats: NerdFontFormats): WidgetItem { + let updatedItem: WidgetItem; + + if (format === formats.defaultFormat) { + updatedItem = removeMetadataKeys(item, [FORMAT_METADATA_KEY]); + } else { + updatedItem = { + ...item, + metadata: { + ...item.metadata, + [FORMAT_METADATA_KEY]: format + } + }; + } + + // The predicate runs against the updated item, since the new format decides + // whether the flag still has a glyph to apply to. + return formats.canUseNerdFont(updatedItem) ? updatedItem : removeNerdFont(updatedItem); +} + +export function toggleNerdFont(item: WidgetItem, formats: NerdFontFormats): WidgetItem { + if (isNerdFontEnabled(item, formats) || !formats.canUseNerdFont(item)) { + return removeNerdFont(item); + } + + return { + ...item, + metadata: { + ...item.metadata, + [NERD_FONT_METADATA_KEY]: 'true' + } + }; +}