From 9767fc63990c6c385e539a003219a2bd0c9f925c Mon Sep 17 00:00:00 2001 From: Matthew Breedlove Date: Wed, 27 Aug 2025 01:03:09 -0400 Subject: [PATCH] Gracefully handle unknown widgets in the settings.json in both the TUI and statusline rendering --- src/tui/components/ColorMenu.tsx | 22 ++++++------- src/tui/components/ItemsEditor.tsx | 31 ++++++++---------- src/tui/components/TerminalOptionsMenu.tsx | 16 ++++++--- src/utils/renderer.ts | 38 +++++++--------------- src/utils/widgets.ts | 10 ++---- 5 files changed, 52 insertions(+), 65 deletions(-) diff --git a/src/tui/components/ColorMenu.tsx b/src/tui/components/ColorMenu.tsx index dc78423..d6f5434 100644 --- a/src/tui/components/ColorMenu.tsx +++ b/src/tui/components/ColorMenu.tsx @@ -43,13 +43,9 @@ export const ColorMenu: React.FC = ({ widgets, lineIndex, settin return showSeparators; } // Use the widget's supportsColors method - try { - const widgetInstance = getWidget(widget.type); - return widgetInstance.supportsColors(widget); - } catch { - // If widget type is not found, exclude it - return false; - } + const widgetInstance = getWidget(widget.type); + // Include unknown widgets (they might support colors, we just don't know) + return widgetInstance ? widgetInstance.supportsColors(widget) : true; }); const [highlightedItemId, setHighlightedItemId] = useState(colorableWidgets[0]?.id ?? null); const [editingBackground, setEditingBackground] = useState(false); @@ -259,7 +255,9 @@ export const ColorMenu: React.FC = ({ widgets, lineIndex, settin let defaultColor = 'white'; if (widget.type !== 'separator' && widget.type !== 'flex-separator') { const widgetImpl = getWidget(widget.type); - defaultColor = widgetImpl.getDefaultColor(); + if (widgetImpl) { + defaultColor = widgetImpl.getDefaultColor(); + } } let currentColor = widget.color ?? defaultColor; // If color is 'dim', treat as if no color was set @@ -313,7 +311,7 @@ export const ColorMenu: React.FC = ({ widgets, lineIndex, settin } const widgetImpl = getWidget(widget.type); - return widgetImpl.getDisplayName(); + return widgetImpl ? widgetImpl.getDisplayName() : `Unknown: ${widget.type}`; }; // Color list for cycling @@ -333,7 +331,9 @@ export const ColorMenu: React.FC = ({ widgets, lineIndex, settin let defaultColor = 'white'; if (widget.type !== 'separator' && widget.type !== 'flex-separator') { const widgetImpl = getWidget(widget.type); - defaultColor = widgetImpl.getDefaultColor(); + if (widgetImpl) { + defaultColor = widgetImpl.getDefaultColor(); + } } const styledLabel = applyColors(label, widget.color ?? defaultColor, widget.backgroundColor, widget.bold, level); return { @@ -363,7 +363,7 @@ export const ColorMenu: React.FC = ({ widgets, lineIndex, settin : (selectedWidget ? (selectedWidget.color ?? (() => { if (selectedWidget.type !== 'separator' && selectedWidget.type !== 'flex-separator') { const widgetImpl = getWidget(selectedWidget.type); - return widgetImpl.getDefaultColor(); + return widgetImpl ? widgetImpl.getDefaultColor() : 'white'; } return 'white'; })()) : 'white'); diff --git a/src/tui/components/ItemsEditor.tsx b/src/tui/components/ItemsEditor.tsx index 358da71..930384e 100644 --- a/src/tui/components/ItemsEditor.tsx +++ b/src/tui/components/ItemsEditor.tsx @@ -263,8 +263,8 @@ export const ItemsEditor: React.FC = ({ widgets, onUpdate, onB // Check for custom widget keybinds const currentWidget = widgets[selectedIndex]; if (currentWidget && currentWidget.type !== 'separator' && currentWidget.type !== 'flex-separator') { - try { - const widgetImpl = getWidget(currentWidget.type); + const widgetImpl = getWidget(currentWidget.type); + if (widgetImpl) { if (widgetImpl.getCustomKeybinds) { const customKeybinds = widgetImpl.getCustomKeybinds(); const matchedKeybind = customKeybinds.find(kb => kb.key === input); @@ -288,8 +288,6 @@ export const ItemsEditor: React.FC = ({ widgets, onUpdate, onB } } } - } catch { - // Widget not found or doesn't support custom keybinds } } } @@ -309,10 +307,13 @@ export const ItemsEditor: React.FC = ({ widgets, onUpdate, onB // Handle regular widgets - delegate to widget for display const widgetImpl = getWidget(widget.type); - const { displayText, modifierText } = widgetImpl.getEditorDisplay(widget); - - // Return plain text without colors - return displayText + (modifierText ? ` ${modifierText}` : ''); + if (widgetImpl) { + const { displayText, modifierText } = widgetImpl.getEditorDisplay(widget); + // Return plain text without colors + return displayText + (modifierText ? ` ${modifierText}` : ''); + } + // Unknown widget type + return `Unknown: ${widget.type}`; }; const hasFlexSeparator = widgets.some(widget => widget.type === 'flex-separator'); @@ -327,14 +328,14 @@ export const ItemsEditor: React.FC = ({ widgets, onUpdate, onB let canToggleRaw = false; let customKeybinds: { key: string; label: string; action: string }[] = []; if (currentWidget && !isSeparator && !isFlexSeparator) { - try { - const widgetImpl = getWidget(currentWidget.type); + const widgetImpl = getWidget(currentWidget.type); + if (widgetImpl) { canToggleRaw = widgetImpl.supportsRawValue(); // Get custom keybinds from the widget if (widgetImpl.getCustomKeybinds) { customKeybinds = widgetImpl.getCustomKeybinds(); } - } catch { + } else { canToggleRaw = false; } } @@ -448,12 +449,8 @@ export const ItemsEditor: React.FC = ({ widgets, onUpdate, onB } else if (currentWidget.type === 'flex-separator') { return 'Expands to fill available terminal width'; } else { - try { - const widgetImpl = getWidget(currentWidget.type); - return widgetImpl.getDescription(); - } catch { - return 'Widget description not available'; - } + const widgetImpl = getWidget(currentWidget.type); + return widgetImpl ? widgetImpl.getDescription() : 'Unknown widget type'; } })()} diff --git a/src/tui/components/TerminalOptionsMenu.tsx b/src/tui/components/TerminalOptionsMenu.tsx index 9629823..fb35c44 100644 --- a/src/tui/components/TerminalOptionsMenu.tsx +++ b/src/tui/components/TerminalOptionsMenu.tsx @@ -60,7 +60,9 @@ export const TerminalOptionsMenu: React.FC = ({ settin if (widget.color?.startsWith('hex:')) { if (widget.type !== 'separator' && widget.type !== 'flex-separator') { const widgetImpl = getWidget(widget.type); - newWidget.color = widgetImpl.getDefaultColor(); + if (widgetImpl) { + newWidget.color = widgetImpl.getDefaultColor(); + } } } if (widget.backgroundColor?.startsWith('hex:')) { @@ -71,7 +73,9 @@ export const TerminalOptionsMenu: React.FC = ({ settin if (widget.color?.startsWith('ansi256:')) { if (widget.type !== 'separator' && widget.type !== 'flex-separator') { const widgetImpl = getWidget(widget.type); - newWidget.color = widgetImpl.getDefaultColor(); + if (widgetImpl) { + newWidget.color = widgetImpl.getDefaultColor(); + } } } if (widget.backgroundColor?.startsWith('ansi256:')) { @@ -82,7 +86,9 @@ export const TerminalOptionsMenu: React.FC = ({ settin if (widget.color?.startsWith('ansi256:') || widget.color?.startsWith('hex:')) { if (widget.type !== 'separator' && widget.type !== 'flex-separator') { const widgetImpl = getWidget(widget.type); - newWidget.color = widgetImpl.getDefaultColor(); + if (widgetImpl) { + newWidget.color = widgetImpl.getDefaultColor(); + } } } if (widget.backgroundColor?.startsWith('ansi256:') || widget.backgroundColor?.startsWith('hex:')) { @@ -118,7 +124,9 @@ export const TerminalOptionsMenu: React.FC = ({ settin if (widget.color?.startsWith('ansi256:') || widget.color?.startsWith('hex:')) { if (widget.type !== 'separator' && widget.type !== 'flex-separator') { const widgetImpl = getWidget(widget.type); - newWidget.color = widgetImpl.getDefaultColor(); + if (widgetImpl) { + newWidget.color = widgetImpl.getDefaultColor(); + } } } if (widget.backgroundColor?.startsWith('ansi256:') || widget.backgroundColor?.startsWith('hex:')) { diff --git a/src/utils/renderer.ts b/src/utils/renderer.ts index 6bdb289..bf32bb0 100644 --- a/src/utils/renderer.ts +++ b/src/utils/renderer.ts @@ -134,11 +134,9 @@ function renderPowerlineStatusLine( if (preRendered?.content) { widgetText = preRendered.content; // Get default color from widget impl for consistency - try { - const widgetImpl = getWidget(widget.type); + const widgetImpl = getWidget(widget.type); + if (widgetImpl) { defaultColor = widgetImpl.getDefaultColor(); - } catch { - // Keep default } } @@ -481,20 +479,14 @@ export function preRenderAllWidgets( continue; } - let widgetText = ''; - try { - const widgetImpl = getWidget(widget.type); - widgetText = widgetImpl.render(widget, context, settings) ?? ''; - } catch { - // Unknown widget type - store empty content - preRenderedLine.push({ - content: '', - plainLength: 0, - widget - }); + const widgetImpl = getWidget(widget.type); + if (!widgetImpl) { + // Unknown widget type - skip it entirely continue; } + const widgetText = widgetImpl.render(widget, context, settings) ?? ''; + // Store the rendered content without padding (padding is applied later) const plainLength = widgetText.replace(ANSI_REGEX, '').length; preRenderedLine.push({ @@ -664,12 +656,8 @@ export function renderStatusLine( // Get the previous widget's colors let widgetColor = prevWidget.color; if (!widgetColor) { - try { - const widgetImpl = getWidget(prevWidget.type); - widgetColor = widgetImpl.getDefaultColor(); - } catch { - widgetColor = 'white'; - } + const widgetImpl = getWidget(prevWidget.type); + widgetColor = widgetImpl ? widgetImpl.getDefaultColor() : 'white'; } separatorColor = widgetColor; separatorBg = prevWidget.backgroundColor; @@ -697,11 +685,9 @@ export function renderStatusLine( if (preRendered?.content) { widgetText = preRendered.content; // Get default color from widget impl for consistency - try { - const widgetImpl = getWidget(widget.type); + const widgetImpl = getWidget(widget.type); + if (widgetImpl) { defaultColor = widgetImpl.getDefaultColor(); - } catch { - // Keep default } } @@ -790,7 +776,7 @@ export function renderStatusLine( let widgetColor = prevElem.widget.color; if (!widgetColor && prevElem.widget.type !== 'separator' && prevElem.widget.type !== 'flex-separator') { const widgetImpl = getWidget(prevElem.widget.type); - widgetColor = widgetImpl.getDefaultColor(); + widgetColor = widgetImpl ? widgetImpl.getDefaultColor() : 'white'; } const coloredSep = applyColorsWithOverride(defaultSep, widgetColor, prevElem.widget.backgroundColor, prevElem.widget.bold); finalElements.push(coloredSep); diff --git a/src/utils/widgets.ts b/src/utils/widgets.ts index 9b03ab6..ee91771 100644 --- a/src/utils/widgets.ts +++ b/src/utils/widgets.ts @@ -28,12 +28,8 @@ const widgetRegistry = new Map([ ['custom-command', new widgets.CustomCommandWidget()] ]); -export function getWidget(type: WidgetItemType): Widget { - const widget = widgetRegistry.get(type); - if (!widget) { - throw new Error(`Unknown widget type: ${type}`); - } - return widget; +export function getWidget(type: WidgetItemType): Widget | null { + return widgetRegistry.get(type) ?? null; } export function getAllWidgetTypes(settings: Settings): WidgetItemType[] { @@ -50,7 +46,7 @@ export function getAllWidgetTypes(settings: Settings): WidgetItemType[] { return allTypes; } -export function isWidgetType(type: string): boolean { +export function isKnownWidgetType(type: string): boolean { return widgetRegistry.has(type) || type === 'separator' || type === 'flex-separator';