/** * @license * Copyright 2025 Vybestack LLC * SPDX-License-Identifier: Apache-2.0 */ import type { LoadedSettings, SettingScope, Settings, } from '../../config/settings.js'; import { getScopeMessageForSetting } from '../../utils/dialogScopeUtils.js'; import { isDefaultValue } from '../../utils/settingsUtils.js'; import { Colors } from '../colors.js'; import { computeDisplayValueForItem } from './settingsDialogDisplay.js'; import { settingValueColor } from './settingsDialogHelpers.js'; import type { PendingValue, SettingItem } from './settingsDialogTypes.js'; import { RadioButtonSelect } from './shared/RadioButtonSelect.js'; import { Box, Text } from 'ink'; import React from 'react'; import { getBorderStyle } from '../contexts/UnicodeRenderingContext.js'; interface TextInputProps { focus: boolean; value: string; placeholder?: string; } /** * Simple text input component for search. */ function TextInput({ focus, value, placeholder }: TextInputProps) { const showPlaceholder = value === '' && placeholder !== undefined; if (showPlaceholder) { return {placeholder}; } const text = value; const cursorPos = text.length; const beforeCursor = text.slice(0, cursorPos); const atCursor = text[cursorPos] ?? ' '; const afterCursor = text.slice(cursorPos + 1); return ( <> {beforeCursor} {focus && ( {atCursor} )} {!focus && {atCursor}} {afterCursor} ); } // --- SettingItemRow component --- interface SettingItemRowProps { item: SettingItem; idx: number; scrollOffset: number; focusSection: 'settings' | 'scope'; activeSettingIndex: number; maxLabelOrDescriptionWidth: number; displayValue: string; selectedScope: SettingScope; settings: LoadedSettings; } function SettingItemRow({ item, idx, scrollOffset, focusSection, activeSettingIndex, maxLabelOrDescriptionWidth, displayValue, selectedScope, settings, }: SettingItemRowProps) { const isActive = focusSection === 'settings' && activeSettingIndex === idx + scrollOffset; const scopeSettings = settings.forScope(selectedScope).settings; const shouldBeGreyedOut = isDefaultValue(item.value, scopeSettings); const scopeMessage = getScopeMessageForSetting( item.value, selectedScope, settings, ); return ( {isActive ? '●' : ''} {item.label} {scopeMessage && {scopeMessage}} {item.description ?? ''} {displayValue} ); } // --- Layout component --- interface SettingsDialogLayoutProps { focusSection: 'settings' | 'scope'; editingKey: string | null; isSearching: boolean; searchQuery: string; subSettingsMode: { isActive: boolean; parentKey: string; parentLabel: string; }; visibleItems: SettingItem[]; scrollOffset: number; activeSettingIndex: number; maxLabelOrDescriptionWidth: number; showScopeSelection: boolean; showRestartPrompt: boolean; selectedScope: SettingScope; settings: LoadedSettings; scopeItems: Array<{ key: string; value: SettingScope; label: string }>; editBuffer: string; editCursorPos: number; cursorVisible: boolean; pendingSettings: Settings; modifiedSettings: Set; globalPendingChanges: Map; handleScopeSelect: (scope: SettingScope) => void; handleScopeHighlight: (scope: SettingScope) => void; } export function SettingsDialogLayout( props: SettingsDialogLayoutProps, ): React.JSX.Element { return ( ); } function SettingsDialogHeader({ focusSection, editingKey, subSettingsMode, isSearching, searchQuery, }: Pick< SettingsDialogLayoutProps, | 'focusSection' | 'editingKey' | 'subSettingsMode' | 'isSearching' | 'searchQuery' >): React.JSX.Element { const borderColor = editingKey === null && focusSection === 'settings' ? Colors.AccentGreen : Colors.Gray; const title = subSettingsMode.isActive ? `${subSettingsMode.parentLabel} > Settings` : 'Settings'; return ( <> {focusSection === 'settings' ? '> ' : ' '} {title}{' '} ); } type SettingsListContentProps = Pick< SettingsDialogLayoutProps, | 'searchQuery' | 'visibleItems' | 'scrollOffset' | 'focusSection' | 'activeSettingIndex' | 'maxLabelOrDescriptionWidth' | 'selectedScope' | 'settings' | 'editingKey' | 'editBuffer' | 'editCursorPos' | 'cursorVisible' | 'pendingSettings' | 'modifiedSettings' | 'globalPendingChanges' | 'subSettingsMode' >; function SettingsListContent( props: SettingsListContentProps, ): React.JSX.Element { if (props.searchQuery !== '' && props.visibleItems.length === 0) { return ( No matches found. ); } return ( <> {props.visibleItems.map((item, idx) => ( ))} ); } function SettingsScopeSection({ showScopeSelection, focusSection, scopeItems, selectedScope, handleScopeSelect, handleScopeHighlight, }: Pick< SettingsDialogLayoutProps, | 'showScopeSelection' | 'focusSection' | 'scopeItems' | 'selectedScope' | 'handleScopeSelect' | 'handleScopeHighlight' >): React.JSX.Element { return ( <> {showScopeSelection && ( {focusSection === 'scope' ? '> ' : ' '}Apply To item.value === selectedScope, )} onSelect={handleScopeSelect} onHighlight={handleScopeHighlight} isFocused={focusSection === 'scope'} showNumbers={focusSection === 'scope'} /> )} ); } function SettingsDialogFooter({ isSearching, showScopeSelection, showRestartPrompt, }: Pick< SettingsDialogLayoutProps, 'isSearching' | 'showScopeSelection' | 'showRestartPrompt' >): React.JSX.Element { const focusHelp = showScopeSelection ? 'Use Enter to select, Tab to change focus' : 'Use Enter to select'; const helpText = isSearching ? '(Type to search, Esc to clear, Enter to navigate)' : `(${focusHelp}, / to search, Esc to close)`; return ( <> {helpText} {showRestartPrompt && ( To see changes, LLxprt Code must be restarted. Press r to exit and apply changes now. )} ); }