diff options
| author | Jacob Richman <[email protected]> | 2025-08-21 16:43:56 -0700 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-08-21 23:43:56 +0000 |
| commit | 29699274bb0e8f70b9bedad40ca2d03739318853 (patch) | |
| tree | 17ff82d54991e72deba93eb40e4b7d068b881cb5 /packages/cli/src/ui/components/SettingsDialog.tsx | |
| parent | 10286934e6a549dcad557adecfc087552e13c983 (diff) | |
feat(settings) support editing string settings. (#6732)
Diffstat (limited to 'packages/cli/src/ui/components/SettingsDialog.tsx')
| -rw-r--r-- | packages/cli/src/ui/components/SettingsDialog.tsx | 113 |
1 files changed, 77 insertions, 36 deletions
diff --git a/packages/cli/src/ui/components/SettingsDialog.tsx b/packages/cli/src/ui/components/SettingsDialog.tsx index 8fa689d0..c9685cd5 100644 --- a/packages/cli/src/ui/components/SettingsDialog.tsx +++ b/packages/cli/src/ui/components/SettingsDialog.tsx @@ -35,7 +35,7 @@ import { import { useVimMode } from '../contexts/VimModeContext.js'; import { useKeypress } from '../hooks/useKeypress.js'; import chalk from 'chalk'; -import { cpSlice, cpLen } from '../utils/textUtils.js'; +import { cpSlice, cpLen, stripUnsafeCharacters } from '../utils/textUtils.js'; interface SettingsDialogProps { settings: LoadedSettings; @@ -78,8 +78,8 @@ export function SettingsDialog({ new Set(), ); - // Preserve pending changes across scope switches (boolean and number values only) - type PendingValue = boolean | number; + // Preserve pending changes across scope switches + type PendingValue = boolean | number | string; const [globalPendingChanges, setGlobalPendingChanges] = useState< Map<string, PendingValue> >(new Map()); @@ -99,7 +99,10 @@ export function SettingsDialog({ const def = getSettingDefinition(key); if (def?.type === 'boolean' && typeof value === 'boolean') { updated = setPendingSettingValue(key, value, updated); - } else if (def?.type === 'number' && typeof value === 'number') { + } else if ( + (def?.type === 'number' && typeof value === 'number') || + (def?.type === 'string' && typeof value === 'string') + ) { updated = setPendingSettingValueAny(key, value, updated); } newModified.add(key); @@ -123,7 +126,7 @@ export function SettingsDialog({ type: definition?.type, toggle: () => { if (definition?.type !== 'boolean') { - // For non-boolean (e.g., number) items, toggle will be handled via edit mode. + // For non-boolean items, toggle will be handled via edit mode. return; } const currentValue = getSettingValue(key, pendingSettings, {}); @@ -220,7 +223,7 @@ export function SettingsDialog({ const items = generateSettingsItems(); - // Number edit state + // Generic edit state const [editingKey, setEditingKey] = useState<string | null>(null); const [editBuffer, setEditBuffer] = useState<string>(''); const [editCursorPos, setEditCursorPos] = useState<number>(0); // Cursor position within edit buffer @@ -235,28 +238,39 @@ export function SettingsDialog({ return () => clearInterval(id); }, [editingKey]); - const startEditingNumber = (key: string, initial?: string) => { + const startEditing = (key: string, initial?: string) => { setEditingKey(key); const initialValue = initial ?? ''; setEditBuffer(initialValue); setEditCursorPos(cpLen(initialValue)); // Position cursor at end of initial value }; - const commitNumberEdit = (key: string) => { - if (editBuffer.trim() === '') { - // Nothing entered; cancel edit + const commitEdit = (key: string) => { + const definition = getSettingDefinition(key); + const type = definition?.type; + + if (editBuffer.trim() === '' && type === 'number') { + // Nothing entered for a number; cancel edit setEditingKey(null); setEditBuffer(''); setEditCursorPos(0); return; } - const parsed = Number(editBuffer.trim()); - if (Number.isNaN(parsed)) { - // Invalid number; cancel edit - setEditingKey(null); - setEditBuffer(''); - setEditCursorPos(0); - return; + + let parsed: string | number; + if (type === 'number') { + const numParsed = Number(editBuffer.trim()); + if (Number.isNaN(numParsed)) { + // Invalid number; cancel edit + setEditingKey(null); + setEditBuffer(''); + setEditCursorPos(0); + return; + } + parsed = numParsed; + } else { + // For strings, use the buffer as is. + parsed = editBuffer; } // Update pending @@ -347,10 +361,16 @@ export function SettingsDialog({ setFocusSection((prev) => (prev === 'settings' ? 'scope' : 'settings')); } if (focusSection === 'settings') { - // If editing a number, capture numeric input and control keys + // If editing, capture input and control keys if (editingKey) { + const definition = getSettingDefinition(editingKey); + const type = definition?.type; + if (key.paste && key.sequence) { - const pasted = key.sequence.replace(/[^0-9\-+.]/g, ''); + let pasted = key.sequence; + if (type === 'number') { + pasted = key.sequence.replace(/[^0-9\-+.]/g, ''); + } if (pasted) { setEditBuffer((b) => { const before = cpSlice(b, 0, editCursorPos); @@ -380,16 +400,27 @@ export function SettingsDialog({ return; } if (name === 'escape') { - commitNumberEdit(editingKey); + commitEdit(editingKey); return; } if (name === 'return') { - commitNumberEdit(editingKey); + commitEdit(editingKey); return; } - // Allow digits, minus, plus, and dot - const ch = key.sequence; - if (/[0-9\-+.]/.test(ch)) { + + let ch = key.sequence; + let isValidChar = false; + if (type === 'number') { + // Allow digits, minus, plus, and dot. + isValidChar = /[0-9\-+.]/.test(ch); + } else { + ch = stripUnsafeCharacters(ch); + // For strings, allow any single character that isn't a control + // sequence. + isValidChar = ch.length === 1; + } + + if (isValidChar) { setEditBuffer((currentBuffer) => { const beforeCursor = cpSlice(currentBuffer, 0, editCursorPos); const afterCursor = cpSlice(currentBuffer, editCursorPos); @@ -398,6 +429,7 @@ export function SettingsDialog({ setEditCursorPos((pos) => pos + 1); return; } + // Arrow key navigation if (name === 'left') { setEditCursorPos((pos) => Math.max(0, pos - 1)); @@ -422,7 +454,7 @@ export function SettingsDialog({ if (name === 'up' || name === 'k') { // If editing, commit first if (editingKey) { - commitNumberEdit(editingKey); + commitEdit(editingKey); } const newIndex = activeSettingIndex > 0 ? activeSettingIndex - 1 : items.length - 1; @@ -436,7 +468,7 @@ export function SettingsDialog({ } else if (name === 'down' || name === 'j') { // If editing, commit first if (editingKey) { - commitNumberEdit(editingKey); + commitEdit(editingKey); } const newIndex = activeSettingIndex < items.length - 1 ? activeSettingIndex + 1 : 0; @@ -449,15 +481,18 @@ export function SettingsDialog({ } } else if (name === 'return' || name === 'space') { const currentItem = items[activeSettingIndex]; - if (currentItem?.type === 'number') { - startEditingNumber(currentItem.value); + if ( + currentItem?.type === 'number' || + currentItem?.type === 'string' + ) { + startEditing(currentItem.value); } else { currentItem?.toggle(); } } else if (/^[0-9]$/.test(key.sequence || '') && !editingKey) { const currentItem = items[activeSettingIndex]; if (currentItem?.type === 'number') { - startEditingNumber(currentItem.value, key.sequence); + startEditing(currentItem.value, key.sequence); } } else if (ctrl && (name === 'c' || name === 'l')) { // Ctrl+C or Ctrl+L: Clear current setting and reset to default @@ -475,8 +510,11 @@ export function SettingsDialog({ prev, ), ); - } else if (defType === 'number') { - if (typeof defaultValue === 'number') { + } else if (defType === 'number' || defType === 'string') { + if ( + typeof defaultValue === 'number' || + typeof defaultValue === 'string' + ) { setPendingSettings((prev) => setPendingSettingValueAny( currentSetting.value, @@ -509,7 +547,8 @@ export function SettingsDialog({ ? typeof defaultValue === 'boolean' ? defaultValue : false - : typeof defaultValue === 'number' + : typeof defaultValue === 'number' || + typeof defaultValue === 'string' ? defaultValue : undefined; const immediateSettingsObject = @@ -541,7 +580,9 @@ export function SettingsDialog({ (currentSetting.type === 'boolean' && typeof defaultValue === 'boolean') || (currentSetting.type === 'number' && - typeof defaultValue === 'number') + typeof defaultValue === 'number') || + (currentSetting.type === 'string' && + typeof defaultValue === 'string') ) { setGlobalPendingChanges((prev) => { const next = new Map(prev); @@ -584,7 +625,7 @@ export function SettingsDialog({ } if (name === 'escape') { if (editingKey) { - commitNumberEdit(editingKey); + commitEdit(editingKey); } else { onSelect(undefined, selectedScope); } @@ -637,8 +678,8 @@ export function SettingsDialog({ // Cursor not visible displayValue = editBuffer; } - } else if (item.type === 'number') { - // For numbers, get the actual current value from pending settings + } else if (item.type === 'number' || item.type === 'string') { + // For numbers/strings, get the actual current value from pending settings const path = item.value.split('.'); const currentValue = getNestedValue(pendingSettings, path); |
