diff --git a/docs/users/configuration/settings.md b/docs/users/configuration/settings.md index 613b819a4..03a029a6a 100644 --- a/docs/users/configuration/settings.md +++ b/docs/users/configuration/settings.md @@ -271,7 +271,6 @@ If you are experiencing performance issues with file searching (e.g., with `@` c | `tools.enableToolOutputTruncation` | boolean | Enable truncation of large tool outputs. | `true` | Requires restart: Yes | | `tools.truncateToolOutputThreshold` | number | Truncate tool output if it is larger than this many characters. Applies to Shell, Grep, Glob, ReadFile and ReadManyFiles tools. | `25000` | Requires restart: Yes | | `tools.truncateToolOutputLines` | number | Maximum lines or entries kept when truncating tool output. Applies to Shell, Grep, Glob, ReadFile and ReadManyFiles tools. | `1000` | Requires restart: Yes | -| `tools.autoAccept` | boolean | Controls whether the CLI automatically accepts and executes tool calls that are considered safe (e.g., read-only operations) without explicit user confirmation. If set to `true`, the CLI will bypass the confirmation prompt for tools deemed safe. | `false` | | #### mcp diff --git a/packages/cli/src/config/settingsSchema.test.ts b/packages/cli/src/config/settingsSchema.test.ts index 68c60759b..adbc162b5 100644 --- a/packages/cli/src/config/settingsSchema.test.ts +++ b/packages/cli/src/config/settingsSchema.test.ts @@ -168,14 +168,14 @@ describe('SettingsSchema', () => { ).toBe(true); expect( getSettingsSchema().ui.properties.hideWindowTitle.showInDialog, - ).toBe(true); + ).toBe(false); expect(getSettingsSchema().ui.properties.hideTips.showInDialog).toBe( true, ); expect( getSettingsSchema().privacy.properties.usageStatisticsEnabled .showInDialog, - ).toBe(false); + ).toBe(true); // Check that advanced settings are hidden from dialog expect(getSettingsSchema().security.properties.auth.showInDialog).toBe( @@ -188,7 +188,7 @@ describe('SettingsSchema', () => { expect(getSettingsSchema().telemetry.showInDialog).toBe(false); // Check that some settings are appropriately hidden - expect(getSettingsSchema().ui.properties.theme.showInDialog).toBe(false); // Changed to false + expect(getSettingsSchema().ui.properties.theme.showInDialog).toBe(true); expect(getSettingsSchema().ui.properties.customThemes.showInDialog).toBe( false, ); // Managed via theme editor @@ -197,13 +197,13 @@ describe('SettingsSchema', () => { ).toBe(false); // Experimental feature expect(getSettingsSchema().ui.properties.accessibility.showInDialog).toBe( false, - ); // Changed to false + ); expect( getSettingsSchema().context.properties.fileFiltering.showInDialog, - ).toBe(false); // Changed to false + ).toBe(false); expect( getSettingsSchema().general.properties.preferredEditor.showInDialog, - ).toBe(false); // Changed to false + ).toBe(true); expect( getSettingsSchema().advanced.properties.autoConfigureMemory .showInDialog, @@ -281,7 +281,7 @@ describe('SettingsSchema', () => { expect( getSettingsSchema().security.properties.folderTrust.properties.enabled .showInDialog, - ).toBe(true); + ).toBe(false); }); it('should have debugKeystrokeLogging setting in schema', () => { @@ -304,7 +304,7 @@ describe('SettingsSchema', () => { expect( getSettingsSchema().general.properties.debugKeystrokeLogging .showInDialog, - ).toBe(true); + ).toBe(false); expect( getSettingsSchema().general.properties.debugKeystrokeLogging .description, diff --git a/packages/cli/src/config/settingsSchema.ts b/packages/cli/src/config/settingsSchema.ts index 6df10b547..d51585826 100644 --- a/packages/cli/src/config/settingsSchema.ts +++ b/packages/cli/src/config/settingsSchema.ts @@ -132,7 +132,7 @@ const SETTINGS_SCHEMA = { requiresRestart: false, default: undefined as string | undefined, description: 'The preferred editor to open files in.', - showInDialog: false, + showInDialog: true, }, vimMode: { type: 'boolean', @@ -163,13 +163,13 @@ const SETTINGS_SCHEMA = { }, gitCoAuthor: { type: 'boolean', - label: 'Git Co-Author', + label: 'Add AI Co-Author to Commits', category: 'General', requiresRestart: false, default: true, description: 'Automatically add a Co-authored-by trailer to git commit messages when commits are made through Qwen Code.', - showInDialog: false, + showInDialog: true, }, checkpointing: { type: 'object', @@ -198,13 +198,13 @@ const SETTINGS_SCHEMA = { requiresRestart: false, default: false, description: 'Enable debug logging of keystrokes to the console.', - showInDialog: true, + showInDialog: false, }, language: { type: 'enum', label: 'Language', category: 'General', - requiresRestart: false, + requiresRestart: true, default: 'auto', description: 'The language for the user interface. Use "auto" to detect from system settings. ' + @@ -221,7 +221,7 @@ const SETTINGS_SCHEMA = { }, terminalBell: { type: 'boolean', - label: 'Terminal Bell', + label: 'Terminal Bell Notification', category: 'General', requiresRestart: false, default: true, @@ -257,7 +257,7 @@ const SETTINGS_SCHEMA = { requiresRestart: false, default: 'text', description: 'The format of the CLI output.', - showInDialog: true, + showInDialog: false, options: [ { value: 'text', label: 'Text' }, { value: 'json', label: 'JSON' }, @@ -280,9 +280,9 @@ const SETTINGS_SCHEMA = { label: 'Theme', category: 'UI', requiresRestart: false, - default: undefined as string | undefined, + default: 'Qwen Dark' as string, description: 'The color theme for the UI.', - showInDialog: false, + showInDialog: true, }, customThemes: { type: 'object', @@ -300,7 +300,7 @@ const SETTINGS_SCHEMA = { requiresRestart: true, default: false, description: 'Hide the window title bar', - showInDialog: true, + showInDialog: false, }, showStatusInTitle: { type: 'boolean', @@ -310,7 +310,7 @@ const SETTINGS_SCHEMA = { default: false, description: 'Show Qwen Code status and thoughts in the terminal window title', - showInDialog: true, + showInDialog: false, }, hideTips: { type: 'boolean', @@ -323,11 +323,11 @@ const SETTINGS_SCHEMA = { }, showLineNumbers: { type: 'boolean', - label: 'Show Line Numbers', + label: 'Show Line Numbers in Code', category: 'UI', requiresRestart: false, default: false, - description: 'Show line numbers in the chat.', + description: 'Show line numbers in the code output.', showInDialog: true, }, showCitations: { @@ -337,7 +337,7 @@ const SETTINGS_SCHEMA = { requiresRestart: false, default: false, description: 'Show citations for generated text in the chat.', - showInDialog: true, + showInDialog: false, }, customWittyPhrases: { type: 'array', @@ -350,7 +350,7 @@ const SETTINGS_SCHEMA = { }, enableWelcomeBack: { type: 'boolean', - label: 'Enable Welcome Back', + label: 'Show Welcome Back Dialog', category: 'UI', requiresRestart: false, default: true, @@ -374,7 +374,7 @@ const SETTINGS_SCHEMA = { requiresRestart: true, default: false, description: 'Disable loading phrases for accessibility', - showInDialog: true, + showInDialog: false, }, screenReader: { type: 'boolean', @@ -384,7 +384,7 @@ const SETTINGS_SCHEMA = { default: undefined as boolean | undefined, description: 'Render output in plain-text to be more screen reader accessible', - showInDialog: true, + showInDialog: false, }, }, }, @@ -402,7 +402,7 @@ const SETTINGS_SCHEMA = { properties: { enabled: { type: 'boolean', - label: 'IDE Mode', + label: 'Auto-connect to IDE', category: 'IDE', requiresRestart: true, default: false, @@ -437,7 +437,7 @@ const SETTINGS_SCHEMA = { requiresRestart: true, default: true, description: 'Enable collection of usage statistics', - showInDialog: false, + showInDialog: true, }, }, }, @@ -478,7 +478,7 @@ const SETTINGS_SCHEMA = { default: -1, description: 'Maximum number of user/model/tool turns to keep in a session. -1 means unlimited.', - showInDialog: true, + showInDialog: false, }, summarizeToolOutput: { type: 'object', @@ -516,7 +516,7 @@ const SETTINGS_SCHEMA = { requiresRestart: false, default: true, description: 'Skip the next speaker check.', - showInDialog: true, + showInDialog: false, }, skipLoopDetection: { type: 'boolean', @@ -525,7 +525,7 @@ const SETTINGS_SCHEMA = { requiresRestart: false, default: false, description: 'Disable all loop detection checks (streaming and LLM).', - showInDialog: true, + showInDialog: false, }, skipStartupContext: { type: 'boolean', @@ -535,7 +535,7 @@ const SETTINGS_SCHEMA = { default: false, description: 'Avoid sending the workspace startup context at the beginning of each session.', - showInDialog: true, + showInDialog: false, }, enableOpenAILogging: { type: 'boolean', @@ -544,7 +544,7 @@ const SETTINGS_SCHEMA = { requiresRestart: false, default: false, description: 'Enable OpenAI logging.', - showInDialog: true, + showInDialog: false, }, openAILoggingDir: { type: 'string', @@ -554,7 +554,7 @@ const SETTINGS_SCHEMA = { default: undefined as string | undefined, description: 'Custom directory path for OpenAI API logs. If not specified, defaults to logs/openai in the current working directory.', - showInDialog: true, + showInDialog: false, }, generationConfig: { type: 'object', @@ -574,7 +574,7 @@ const SETTINGS_SCHEMA = { description: 'Request timeout in milliseconds.', parentKey: 'generationConfig', childKey: 'timeout', - showInDialog: true, + showInDialog: false, }, maxRetries: { type: 'number', @@ -585,7 +585,7 @@ const SETTINGS_SCHEMA = { description: 'Maximum number of retries for failed requests.', parentKey: 'generationConfig', childKey: 'maxRetries', - showInDialog: true, + showInDialog: false, }, disableCacheControl: { type: 'boolean', @@ -596,7 +596,7 @@ const SETTINGS_SCHEMA = { description: 'Disable cache control for DashScope providers.', parentKey: 'generationConfig', childKey: 'disableCacheControl', - showInDialog: true, + showInDialog: false, }, schemaCompliance: { type: 'enum', @@ -608,7 +608,7 @@ const SETTINGS_SCHEMA = { 'The compliance mode for tool schemas sent to the model. Use "openapi_30" for strict OpenAPI 3.0 compatibility (e.g., for Gemini).', parentKey: 'generationConfig', childKey: 'schemaCompliance', - showInDialog: true, + showInDialog: false, options: [ { value: 'auto', label: 'Auto (Default)' }, { value: 'openapi_30', label: 'OpenAPI 3.0 Strict' }, @@ -653,7 +653,7 @@ const SETTINGS_SCHEMA = { requiresRestart: false, default: 200, description: 'Maximum number of directories to search for memory.', - showInDialog: true, + showInDialog: false, }, includeDirectories: { type: 'array', @@ -673,7 +673,7 @@ const SETTINGS_SCHEMA = { requiresRestart: false, default: false, description: 'Whether to load memory files from include directories.', - showInDialog: true, + showInDialog: false, }, fileFiltering: { type: 'object', @@ -709,7 +709,7 @@ const SETTINGS_SCHEMA = { requiresRestart: true, default: true, description: 'Enable recursive file search functionality', - showInDialog: true, + showInDialog: false, }, disableFuzzySearch: { type: 'boolean', @@ -718,7 +718,7 @@ const SETTINGS_SCHEMA = { requiresRestart: true, default: false, description: 'Disable fuzzy search when searching for files.', - showInDialog: true, + showInDialog: false, }, }, }, @@ -755,7 +755,7 @@ const SETTINGS_SCHEMA = { properties: { enableInteractiveShell: { type: 'boolean', - label: 'Enable Interactive Shell', + label: 'Interactive Shell (PTY)', category: 'Tools', requiresRestart: true, default: false, @@ -780,20 +780,10 @@ const SETTINGS_SCHEMA = { requiresRestart: false, default: false, description: 'Show color in shell output.', - showInDialog: true, + showInDialog: false, }, }, }, - autoAccept: { - type: 'boolean', - label: 'Auto Accept', - category: 'Tools', - requiresRestart: false, - default: false, - description: - 'Automatically accept and execute tool calls that are considered safe (e.g., read-only operations).', - showInDialog: true, - }, core: { type: 'array', label: 'Core Tools', @@ -825,7 +815,7 @@ const SETTINGS_SCHEMA = { }, approvalMode: { type: 'enum', - label: 'Approval Mode', + label: 'Tool Approval Mode', category: 'Tools', requiresRestart: false, default: ApprovalMode.DEFAULT, @@ -839,6 +829,16 @@ const SETTINGS_SCHEMA = { { value: ApprovalMode.YOLO, label: 'YOLO' }, ], }, + autoAccept: { + type: 'boolean', + label: 'Auto Accept', + category: 'Tools', + requiresRestart: false, + default: false, + description: + 'Automatically accept and execute tool calls that are considered safe (e.g., read-only operations) without explicit user confirmation.', + showInDialog: false, + }, discoveryCommand: { type: 'string', label: 'Tool Discovery Command', @@ -865,7 +865,7 @@ const SETTINGS_SCHEMA = { default: true, description: 'Use ripgrep for file content search instead of the fallback implementation. Provides faster search performance.', - showInDialog: true, + showInDialog: false, }, useBuiltinRipgrep: { type: 'boolean', @@ -875,7 +875,7 @@ const SETTINGS_SCHEMA = { default: true, description: 'Use the bundled ripgrep binary. When set to false, the system-level "rg" command will be used instead. This setting is only effective when useRipgrep is true.', - showInDialog: true, + showInDialog: false, }, enableToolOutputTruncation: { type: 'boolean', @@ -884,7 +884,7 @@ const SETTINGS_SCHEMA = { requiresRestart: true, default: true, description: 'Enable truncation of large tool outputs.', - showInDialog: true, + showInDialog: false, }, truncateToolOutputThreshold: { type: 'number', @@ -894,7 +894,7 @@ const SETTINGS_SCHEMA = { default: DEFAULT_TRUNCATE_TOOL_OUTPUT_THRESHOLD, description: 'Truncate tool output if it is larger than this many characters. Set to -1 to disable.', - showInDialog: true, + showInDialog: false, }, truncateToolOutputLines: { type: 'number', @@ -903,7 +903,7 @@ const SETTINGS_SCHEMA = { requiresRestart: true, default: DEFAULT_TRUNCATE_TOOL_OUTPUT_LINES, description: 'The number of lines to keep when truncating tool output.', - showInDialog: true, + showInDialog: false, }, }, }, @@ -980,7 +980,7 @@ const SETTINGS_SCHEMA = { requiresRestart: true, default: false, description: 'Setting to track whether Folder trust is enabled.', - showInDialog: true, + showInDialog: false, }, }, }, @@ -1148,7 +1148,7 @@ const SETTINGS_SCHEMA = { default: true, description: 'Enable vision model support and auto-switching functionality. When disabled, vision models like qwen-vl-max-latest will be hidden and auto-switching will not occur.', - showInDialog: true, + showInDialog: false, }, vlmSwitchMode: { type: 'string', diff --git a/packages/cli/src/i18n/locales/de.js b/packages/cli/src/i18n/locales/de.js index 2383ef7df..2e6940a14 100644 --- a/packages/cli/src/i18n/locales/de.js +++ b/packages/cli/src/i18n/locales/de.js @@ -97,8 +97,8 @@ export default { Preview: 'Vorschau', '(Use Enter to select, Tab to configure scope)': '(Enter zum Auswählen, Tab zum Konfigurieren des Bereichs)', - '(Use Enter to apply scope, Tab to select theme)': - '(Enter zum Anwenden des Bereichs, Tab zum Auswählen des Designs)', + '(Use Enter to apply scope, Tab to go back)': + '(Enter zum Anwenden des Bereichs, Tab zum Zurückgehen)', 'Theme configuration unavailable due to NO_COLOR env variable.': 'Design-Konfiguration aufgrund der NO_COLOR-Umgebungsvariable nicht verfügbar.', 'Theme "{{themeName}}" not found.': 'Design "{{themeName}}" nicht gefunden.', @@ -260,8 +260,6 @@ export default { 'View and edit Qwen Code settings': 'Qwen Code Einstellungen anzeigen und bearbeiten', Settings: 'Einstellungen', - '(Use Enter to select{{tabText}})': '(Enter zum Auswählen{{tabText}})', - ', Tab to change focus': ', Tab zum Fokuswechsel', 'To see changes, Qwen Code must be restarted. Press r to exit and apply changes now.': 'Um Änderungen zu sehen, muss Qwen Code neu gestartet werden. Drücken Sie r, um jetzt zu beenden und Änderungen anzuwenden.', 'The command "/{{command}}" is not supported in non-interactive mode.': @@ -271,6 +269,12 @@ export default { // ============================================================================ 'Vim Mode': 'Vim-Modus', 'Disable Auto Update': 'Automatische Updates deaktivieren', + 'Add AI Co-Author to Commits': 'KI als Co-Autor zu Commits hinzufügen', + 'Terminal Bell Notification': 'Terminal-Signalton', + 'Enable Usage Statistics': 'Nutzungsstatistiken aktivieren', + Theme: 'Farbschema', + 'Preferred Editor': 'Bevorzugter Editor', + 'Auto-connect to IDE': 'Automatische Verbindung zur IDE', 'Enable Prompt Completion': 'Eingabevervollständigung aktivieren', 'Debug Keystroke Logging': 'Debug-Protokollierung von Tastatureingaben', Language: 'Sprache', @@ -278,10 +282,10 @@ export default { 'Hide Window Title': 'Fenstertitel ausblenden', 'Show Status in Title': 'Status im Titel anzeigen', 'Hide Tips': 'Tipps ausblenden', - 'Show Line Numbers': 'Zeilennummern anzeigen', + 'Show Line Numbers in Code': 'Zeilennummern im Code anzeigen', 'Show Citations': 'Quellenangaben anzeigen', 'Custom Witty Phrases': 'Benutzerdefinierte Witzige Sprüche', - 'Enable Welcome Back': 'Willkommen-zurück aktivieren', + 'Show Welcome Back Dialog': 'Willkommen-zurück-Dialog anzeigen', 'Disable Loading Phrases': 'Ladesprüche deaktivieren', 'Screen Reader Mode': 'Bildschirmleser-Modus', 'IDE Mode': 'IDE-Modus', @@ -301,7 +305,7 @@ export default { 'Respect .qwenignore': '.qwenignore beachten', 'Enable Recursive File Search': 'Rekursive Dateisuche aktivieren', 'Disable Fuzzy Search': 'Unscharfe Suche deaktivieren', - 'Enable Interactive Shell': 'Interaktive Shell aktivieren', + 'Interactive Shell (PTY)': 'Interaktive Shell (PTY)', 'Show Color': 'Farbe anzeigen', 'Auto Accept': 'Automatisch akzeptieren', 'Use Ripgrep': 'Ripgrep verwenden', @@ -337,6 +341,11 @@ export default { 'Show all directories in the workspace': 'Alle Verzeichnisse im Arbeitsbereich anzeigen', 'set external editor preference': 'Externen Editor festlegen', + 'Select Editor': 'Editor auswählen', + 'Editor Preference': 'Editor-Einstellung', + 'These editors are currently supported. Please note that some editors cannot be used in sandbox mode.': + 'Diese Editoren werden derzeit unterstützt. Bitte beachten Sie, dass einige Editoren nicht im Sandbox-Modus verwendet werden können.', + 'Your preferred editor is:': 'Ihr bevorzugter Editor ist:', 'Manage extensions': 'Erweiterungen verwalten', 'List active extensions': 'Aktive Erweiterungen auflisten', 'Update extensions. Usage: update |--all': @@ -427,7 +436,7 @@ export default { // ============================================================================ // Commands - Approval Mode // ============================================================================ - 'Approval Mode': 'Genehmigungsmodus', + 'Tool Approval Mode': 'Werkzeug-Genehmigungsmodus', 'Current approval mode: {{mode}}': 'Aktueller Genehmigungsmodus: {{mode}}', 'Available approval modes:': 'Verfügbare Genehmigungsmodi:', 'Approval mode changed to: {{mode}}': @@ -469,8 +478,6 @@ export default { 'Automatically approve all tools': 'Alle Werkzeuge automatisch genehmigen', 'Workspace approval mode exists and takes priority. User-level change will have no effect.': 'Arbeitsbereich-Genehmigungsmodus existiert und hat Vorrang. Benutzerebene-Änderung hat keine Wirkung.', - '(Use Enter to select, Tab to change focus)': - '(Enter zum Auswählen, Tab zum Fokuswechsel)', 'Apply To': 'Anwenden auf', 'User Settings': 'Benutzereinstellungen', 'Workspace Settings': 'Arbeitsbereich-Einstellungen', diff --git a/packages/cli/src/i18n/locales/en.js b/packages/cli/src/i18n/locales/en.js index 7db9aec56..be5730a9f 100644 --- a/packages/cli/src/i18n/locales/en.js +++ b/packages/cli/src/i18n/locales/en.js @@ -118,8 +118,8 @@ export default { Preview: 'Preview', '(Use Enter to select, Tab to configure scope)': '(Use Enter to select, Tab to configure scope)', - '(Use Enter to apply scope, Tab to select theme)': - '(Use Enter to apply scope, Tab to select theme)', + '(Use Enter to apply scope, Tab to go back)': + '(Use Enter to apply scope, Tab to go back)', 'Theme configuration unavailable due to NO_COLOR env variable.': 'Theme configuration unavailable due to NO_COLOR env variable.', 'Theme "{{themeName}}" not found.': 'Theme "{{themeName}}" not found.', @@ -277,8 +277,6 @@ export default { // ============================================================================ 'View and edit Qwen Code settings': 'View and edit Qwen Code settings', Settings: 'Settings', - '(Use Enter to select{{tabText}})': '(Use Enter to select{{tabText}})', - ', Tab to change focus': ', Tab to change focus', 'To see changes, Qwen Code must be restarted. Press r to exit and apply changes now.': 'To see changes, Qwen Code must be restarted. Press r to exit and apply changes now.', 'The command "/{{command}}" is not supported in non-interactive mode.': @@ -288,6 +286,12 @@ export default { // ============================================================================ 'Vim Mode': 'Vim Mode', 'Disable Auto Update': 'Disable Auto Update', + 'Add AI Co-Author to Commits': 'Add AI Co-Author to Commits', + 'Terminal Bell Notification': 'Terminal Bell Notification', + 'Enable Usage Statistics': 'Enable Usage Statistics', + Theme: 'Theme', + 'Preferred Editor': 'Preferred Editor', + 'Auto-connect to IDE': 'Auto-connect to IDE', 'Enable Prompt Completion': 'Enable Prompt Completion', 'Debug Keystroke Logging': 'Debug Keystroke Logging', Language: 'Language', @@ -295,10 +299,10 @@ export default { 'Hide Window Title': 'Hide Window Title', 'Show Status in Title': 'Show Status in Title', 'Hide Tips': 'Hide Tips', - 'Show Line Numbers': 'Show Line Numbers', + 'Show Line Numbers in Code': 'Show Line Numbers in Code', 'Show Citations': 'Show Citations', 'Custom Witty Phrases': 'Custom Witty Phrases', - 'Enable Welcome Back': 'Enable Welcome Back', + 'Show Welcome Back Dialog': 'Show Welcome Back Dialog', 'Disable Loading Phrases': 'Disable Loading Phrases', 'Screen Reader Mode': 'Screen Reader Mode', 'IDE Mode': 'IDE Mode', @@ -318,7 +322,7 @@ export default { 'Respect .qwenignore': 'Respect .qwenignore', 'Enable Recursive File Search': 'Enable Recursive File Search', 'Disable Fuzzy Search': 'Disable Fuzzy Search', - 'Enable Interactive Shell': 'Enable Interactive Shell', + 'Interactive Shell (PTY)': 'Interactive Shell (PTY)', 'Show Color': 'Show Color', 'Auto Accept': 'Auto Accept', 'Use Ripgrep': 'Use Ripgrep', @@ -353,6 +357,11 @@ export default { 'Show all directories in the workspace': 'Show all directories in the workspace', 'set external editor preference': 'set external editor preference', + 'Select Editor': 'Select Editor', + 'Editor Preference': 'Editor Preference', + 'These editors are currently supported. Please note that some editors cannot be used in sandbox mode.': + 'These editors are currently supported. Please note that some editors cannot be used in sandbox mode.', + 'Your preferred editor is:': 'Your preferred editor is:', 'Manage extensions': 'Manage extensions', 'List active extensions': 'List active extensions', 'Update extensions. Usage: update |--all': @@ -440,7 +449,7 @@ export default { // ============================================================================ // Commands - Approval Mode // ============================================================================ - 'Approval Mode': 'Approval Mode', + 'Tool Approval Mode': 'Tool Approval Mode', 'Current approval mode: {{mode}}': 'Current approval mode: {{mode}}', 'Available approval modes:': 'Available approval modes:', 'Approval mode changed to: {{mode}}': 'Approval mode changed to: {{mode}}', @@ -479,8 +488,6 @@ export default { 'Automatically approve all tools': 'Automatically approve all tools', 'Workspace approval mode exists and takes priority. User-level change will have no effect.': 'Workspace approval mode exists and takes priority. User-level change will have no effect.', - '(Use Enter to select, Tab to change focus)': - '(Use Enter to select, Tab to change focus)', 'Apply To': 'Apply To', 'User Settings': 'User Settings', 'Workspace Settings': 'Workspace Settings', diff --git a/packages/cli/src/i18n/locales/ru.js b/packages/cli/src/i18n/locales/ru.js index 8a5e7362d..968eafa14 100644 --- a/packages/cli/src/i18n/locales/ru.js +++ b/packages/cli/src/i18n/locales/ru.js @@ -121,8 +121,8 @@ export default { Preview: 'Предпросмотр', '(Use Enter to select, Tab to configure scope)': '(Enter для выбора, Tab для настройки области)', - '(Use Enter to apply scope, Tab to select theme)': - '(Enter для применения области, Tab для выбора темы)', + '(Use Enter to apply scope, Tab to go back)': + '(Enter для применения области, Tab для возврата)', 'Theme configuration unavailable due to NO_COLOR env variable.': 'Настройка темы недоступна из-за переменной окружения NO_COLOR.', 'Theme "{{themeName}}" not found.': 'Тема "{{themeName}}" не найдена.', @@ -281,8 +281,6 @@ export default { // ============================================================================ 'View and edit Qwen Code settings': 'Просмотр и изменение настроек Qwen Code', Settings: 'Настройки', - '(Use Enter to select{{tabText}})': '(Enter для выбора{{tabText}})', - ', Tab to change focus': ', Tab для смены фокуса', 'To see changes, Qwen Code must be restarted. Press r to exit and apply changes now.': 'Для применения изменений необходимо перезапустить Qwen Code. Нажмите r для выхода и применения изменений.', 'The command "/{{command}}" is not supported in non-interactive mode.': @@ -292,6 +290,12 @@ export default { // ============================================================================ 'Vim Mode': 'Режим Vim', 'Disable Auto Update': 'Отключить автообновление', + 'Add AI Co-Author to Commits': 'Добавлять ИИ как соавтора в коммиты', + 'Terminal Bell Notification': 'Звуковое уведомление терминала', + 'Enable Usage Statistics': 'Включить сбор статистики использования', + Theme: 'Тема', + 'Preferred Editor': 'Предпочтительный редактор', + 'Auto-connect to IDE': 'Автоподключение к IDE', 'Enable Prompt Completion': 'Включить автодополнение промптов', 'Debug Keystroke Logging': 'Логирование нажатий клавиш для отладки', Language: 'Язык', @@ -299,10 +303,10 @@ export default { 'Hide Window Title': 'Скрыть заголовок окна', 'Show Status in Title': 'Показывать статус в заголовке', 'Hide Tips': 'Скрыть подсказки', - 'Show Line Numbers': 'Показывать номера строк', + 'Show Line Numbers in Code': 'Показывать номера строк в коде', 'Show Citations': 'Показывать цитаты', 'Custom Witty Phrases': 'Пользовательские остроумные фразы', - 'Enable Welcome Back': 'Включить приветствие при возврате', + 'Show Welcome Back Dialog': 'Показывать диалог приветствия', 'Disable Loading Phrases': 'Отключить фразы при загрузке', 'Screen Reader Mode': 'Режим программы чтения с экрана', 'IDE Mode': 'Режим IDE', @@ -322,7 +326,7 @@ export default { 'Respect .qwenignore': 'Учитывать .qwenignore', 'Enable Recursive File Search': 'Включить рекурсивный поиск файлов', 'Disable Fuzzy Search': 'Отключить нечеткий поиск', - 'Enable Interactive Shell': 'Включить интерактивный терминал', + 'Interactive Shell (PTY)': 'Интерактивный терминал (PTY)', 'Show Color': 'Показывать цвета', 'Auto Accept': 'Автоподтверждение', 'Use Ripgrep': 'Использовать Ripgrep', @@ -359,6 +363,11 @@ export default { 'Показать все директории в рабочем пространстве', 'set external editor preference': 'Установка предпочитаемого внешнего редактора', + 'Select Editor': 'Выбрать редактор', + 'Editor Preference': 'Настройка редактора', + 'These editors are currently supported. Please note that some editors cannot be used in sandbox mode.': + 'В настоящее время поддерживаются следующие редакторы. Обратите внимание, что некоторые редакторы нельзя использовать в режиме песочницы.', + 'Your preferred editor is:': 'Ваш предпочитаемый редактор:', 'Manage extensions': 'Управление расширениями', 'List active extensions': 'Показать активные расширения', 'Update extensions. Usage: update |--all': @@ -448,7 +457,7 @@ export default { // ============================================================================ // Команды - Режим подтверждения // ============================================================================ - 'Approval Mode': 'Режим подтверждения', + 'Tool Approval Mode': 'Режим подтверждения инструментов', 'Current approval mode: {{mode}}': 'Текущий режим подтверждения: {{mode}}', 'Available approval modes:': 'Доступные режимы подтверждения:', 'Approval mode changed to: {{mode}}': @@ -490,8 +499,6 @@ export default { 'Автоматически подтверждать все инструменты', 'Workspace approval mode exists and takes priority. User-level change will have no effect.': 'Режим подтверждения рабочего пространства существует и имеет приоритет. Изменение на уровне пользователя не будет иметь эффекта.', - '(Use Enter to select, Tab to change focus)': - '(Enter для выбора, Tab для смены фокуса)', 'Apply To': 'Применить к', 'User Settings': 'Настройки пользователя', 'Workspace Settings': 'Настройки рабочего пространства', diff --git a/packages/cli/src/i18n/locales/zh.js b/packages/cli/src/i18n/locales/zh.js index 4ee3fc220..038ee9add 100644 --- a/packages/cli/src/i18n/locales/zh.js +++ b/packages/cli/src/i18n/locales/zh.js @@ -117,8 +117,8 @@ export default { Preview: '预览', '(Use Enter to select, Tab to configure scope)': '(使用 Enter 选择,Tab 配置作用域)', - '(Use Enter to apply scope, Tab to select theme)': - '(使用 Enter 应用作用域,Tab 选择主题)', + '(Use Enter to apply scope, Tab to go back)': + '(使用 Enter 应用作用域,Tab 返回)', 'Theme configuration unavailable due to NO_COLOR env variable.': '由于 NO_COLOR 环境变量,主题配置不可用。', 'Theme "{{themeName}}" not found.': '未找到主题 "{{themeName}}"。', @@ -268,8 +268,6 @@ export default { // ============================================================================ 'View and edit Qwen Code settings': '查看和编辑 Qwen Code 设置', Settings: '设置', - '(Use Enter to select{{tabText}})': '(使用 Enter 选择{{tabText}})', - ', Tab to change focus': ',Tab 切换焦点', 'To see changes, Qwen Code must be restarted. Press r to exit and apply changes now.': '要查看更改,必须重启 Qwen Code。按 r 退出并立即应用更改。', 'The command "/{{command}}" is not supported in non-interactive mode.': @@ -279,6 +277,12 @@ export default { // ============================================================================ 'Vim Mode': 'Vim 模式', 'Disable Auto Update': '禁用自动更新', + 'Add AI Co-Author to Commits': '在提交中添加 AI 协作者', + 'Terminal Bell Notification': '终端响铃通知', + 'Enable Usage Statistics': '启用使用统计', + Theme: '主题', + 'Preferred Editor': '首选编辑器', + 'Auto-connect to IDE': '自动连接到 IDE', 'Enable Prompt Completion': '启用提示补全', 'Debug Keystroke Logging': '调试按键记录', Language: '语言', @@ -286,10 +290,10 @@ export default { 'Hide Window Title': '隐藏窗口标题', 'Show Status in Title': '在标题中显示状态', 'Hide Tips': '隐藏提示', - 'Show Line Numbers': '显示行号', + 'Show Line Numbers in Code': '在代码中显示行号', 'Show Citations': '显示引用', 'Custom Witty Phrases': '自定义诙谐短语', - 'Enable Welcome Back': '启用欢迎回来', + 'Show Welcome Back Dialog': '显示欢迎回来对话框', 'Disable Loading Phrases': '禁用加载短语', 'Screen Reader Mode': '屏幕阅读器模式', 'IDE Mode': 'IDE 模式', @@ -308,7 +312,7 @@ export default { 'Respect .qwenignore': '遵守 .qwenignore', 'Enable Recursive File Search': '启用递归文件搜索', 'Disable Fuzzy Search': '禁用模糊搜索', - 'Enable Interactive Shell': '启用交互式 Shell', + 'Interactive Shell (PTY)': '交互式 Shell (PTY)', 'Show Color': '显示颜色', 'Auto Accept': '自动接受', 'Use Ripgrep': '使用 Ripgrep', @@ -340,6 +344,11 @@ export default { '将目录添加到工作区。使用逗号分隔多个路径', 'Show all directories in the workspace': '显示工作区中的所有目录', 'set external editor preference': '设置外部编辑器首选项', + 'Select Editor': '选择编辑器', + 'Editor Preference': '编辑器首选项', + 'These editors are currently supported. Please note that some editors cannot be used in sandbox mode.': + '当前支持以下编辑器。请注意,某些编辑器无法在沙箱模式下使用。', + 'Your preferred editor is:': '您的首选编辑器是:', 'Manage extensions': '管理扩展', 'List active extensions': '列出活动扩展', 'Update extensions. Usage: update |--all': @@ -423,7 +432,7 @@ export default { // ============================================================================ // Commands - Approval Mode // ============================================================================ - 'Approval Mode': '审批模式', + 'Tool Approval Mode': '工具审批模式', 'Current approval mode: {{mode}}': '当前审批模式:{{mode}}', 'Available approval modes:': '可用的审批模式:', 'Approval mode changed to: {{mode}}': '审批模式已更改为:{{mode}}', @@ -457,8 +466,6 @@ export default { 'Automatically approve all tools': '自动批准所有工具', 'Workspace approval mode exists and takes priority. User-level change will have no effect.': '工作区审批模式已存在并具有优先级。用户级别的更改将无效。', - '(Use Enter to select, Tab to change focus)': - '(使用 Enter 选择,Tab 切换焦点)', 'Apply To': '应用于', 'User Settings': '用户设置', 'Workspace Settings': '工作区设置', diff --git a/packages/cli/src/ui/AppContainer.tsx b/packages/cli/src/ui/AppContainer.tsx index 6cf2ea940..852d69b3d 100644 --- a/packages/cli/src/ui/AppContainer.tsx +++ b/packages/cli/src/ui/AppContainer.tsx @@ -1388,6 +1388,8 @@ export const AppContainer = (props: AppContainerProps) => { const uiActions: UIActions = useMemo( () => ({ + openThemeDialog, + openEditorDialog, handleThemeSelect, handleThemeHighlight, handleApprovalModeSelect, @@ -1425,6 +1427,8 @@ export const AppContainer = (props: AppContainerProps) => { handleResume, }), [ + openThemeDialog, + openEditorDialog, handleThemeSelect, handleThemeHighlight, handleApprovalModeSelect, diff --git a/packages/cli/src/ui/components/ApprovalModeDialog.tsx b/packages/cli/src/ui/components/ApprovalModeDialog.tsx index d81b6f4c0..48293ffdc 100644 --- a/packages/cli/src/ui/components/ApprovalModeDialog.tsx +++ b/packages/cli/src/ui/components/ApprovalModeDialog.tsx @@ -54,7 +54,7 @@ export function ApprovalModeDialog({ }: ApprovalModeDialogProps): React.JSX.Element { // Start with User scope by default const [selectedScope, setSelectedScope] = useState( - SettingScope.Workspace, + SettingScope.User, ); // Track the currently highlighted approval mode @@ -90,19 +90,17 @@ export function ApprovalModeDialog({ setSelectedScope(scope); }, []); - const handleScopeSelect = useCallback( - (scope: SettingScope) => { - onSelect(highlightedMode, scope); - }, - [onSelect, highlightedMode], - ); + const handleScopeSelect = useCallback((scope: SettingScope) => { + setSelectedScope(scope); + setMode('mode'); + }, []); - const [focusSection, setFocusSection] = useState<'mode' | 'scope'>('mode'); + const [mode, setMode] = useState<'mode' | 'scope'>('mode'); useKeypress( (key) => { if (key.name === 'tab') { - setFocusSection((prev) => (prev === 'mode' ? 'scope' : 'mode')); + setMode((prev) => (prev === 'mode' ? 'scope' : 'mode')); } if (key.name === 'escape') { onSelect(undefined, selectedScope); @@ -127,59 +125,56 @@ export function ApprovalModeDialog({ - - {/* Approval Mode Selection */} - - {focusSection === 'mode' ? '> ' : ' '} - {t('Approval Mode')}{' '} - {otherScopeModifiedMessage} - - - - - - - {/* Scope Selection */} - - - - - - - {/* Warning when workspace setting will override user setting */} - {showWorkspacePriorityWarning && ( - <> - - ⚠{' '} - {t( - 'Workspace approval mode exists and takes priority. User-level change will have no effect.', - )} + {mode === 'mode' ? ( + + {/* Approval Mode Selection */} + + {mode === 'mode' ? '> ' : ' '} + {t('Approval Mode')}{' '} + + {otherScopeModifiedMessage} - - - )} - - - {t('(Use Enter to select, Tab to change focus)')} + + + + {/* Warning when workspace setting will override user setting */} + {showWorkspacePriorityWarning && ( + + + ⚠{' '} + {t( + 'Workspace approval mode exists and takes priority. User-level change will have no effect.', + )} + + + )} + + ) : ( + + )} + + + {mode === 'mode' + ? t('(Use Enter to select, Tab to configure scope)') + : t('(Use Enter to apply scope, Tab to go back)')} diff --git a/packages/cli/src/ui/components/DialogManager.tsx b/packages/cli/src/ui/components/DialogManager.tsx index 6ff9f4aae..02b514b14 100644 --- a/packages/cli/src/ui/components/DialogManager.tsx +++ b/packages/cli/src/ui/components/DialogManager.tsx @@ -152,12 +152,38 @@ export const DialogManager = ({ ); } + if (uiState.isEditorDialogOpen) { + return ( + + {uiState.editorError && ( + + {uiState.editorError} + + )} + + + ); + } if (uiState.isSettingsDialogOpen) { return ( uiActions.closeSettingsDialog()} + onSelect={(settingName) => { + if (settingName === 'ui.theme') { + uiActions.openThemeDialog(); + return; + } + if (settingName === 'general.preferredEditor') { + uiActions.openEditorDialog(); + return; + } + uiActions.closeSettingsDialog(); + }} onRestartRequest={() => process.exit(0)} availableTerminalHeight={terminalHeight - staticExtraHeight} config={config} @@ -237,22 +263,6 @@ export const DialogManager = ({ ); } } - if (uiState.isEditorDialogOpen) { - return ( - - {uiState.editorError && ( - - {uiState.editorError} - - )} - - - ); - } if (uiState.isPermissionsDialogOpen) { return ( ( SettingScope.User, ); - const [focusedSection, setFocusedSection] = useState<'editor' | 'scope'>( - 'editor', - ); + const [mode, setMode] = useState<'editor' | 'scope'>('editor'); + useKeypress( (key) => { if (key.name === 'tab') { - setFocusedSection((prev) => (prev === 'editor' ? 'scope' : 'editor')); + setMode((prev) => (prev === 'editor' ? 'scope' : 'editor')); } if (key.name === 'escape') { onExit(); @@ -65,23 +65,6 @@ export function EditorSettingsDialog({ editorIndex = 0; } - const scopeItems = [ - { - get label() { - return t('User Settings'); - }, - value: SettingScope.User, - key: SettingScope.User, - }, - { - get label() { - return t('Workspace Settings'); - }, - value: SettingScope.Workspace, - key: SettingScope.Workspace, - }, - ]; - const handleEditorSelect = (editorType: EditorType | 'not_set') => { if (editorType === 'not_set') { onSelect(undefined, selectedScope); @@ -92,7 +75,11 @@ export function EditorSettingsDialog({ const handleScopeSelect = (scope: SettingScope) => { setSelectedScope(scope); - setFocusedSection('editor'); + setMode('editor'); + }; + + const handleScopeHighlight = (scope: SettingScope) => { + setSelectedScope(scope); }; let otherScopeModifiedMessage = ''; @@ -131,54 +118,59 @@ export function EditorSettingsDialog({ width="100%" > - - {focusedSection === 'editor' ? '> ' : ' '}Select Editor{' '} - {otherScopeModifiedMessage} - - ({ - label: item.name, - value: item.type, - disabled: item.disabled, - key: item.type, - }))} - initialIndex={editorIndex} - onSelect={handleEditorSelect} - isFocused={focusedSection === 'editor'} - key={selectedScope} - /> - - - - {focusedSection === 'scope' ? '> ' : ' '} - {t('Apply To')} - - + + {mode === 'editor' ? '> ' : ' '} + {t('Select Editor')}{' '} + + {otherScopeModifiedMessage} + + + + ({ + label: item.name, + value: item.type, + disabled: item.disabled, + key: item.type, + }))} + initialIndex={editorIndex} + onSelect={handleEditorSelect} + isFocused={mode === 'editor'} + key={selectedScope} + /> + + ) : ( + - + )} - - (Use Enter to select, Tab to change focus) + + {mode === 'editor' + ? t('(Use Enter to select, Tab to configure scope)') + : t('(Use Enter to apply scope, Tab to go back)')} - Editor Preference + {t('Editor Preference')} - These editors are currently supported. Please note that some editors - cannot be used in sandbox mode. + {t( + 'These editors are currently supported. Please note that some editors cannot be used in sandbox mode.', + )} - Your preferred editor is:{' '} + {t('Your preferred editor is:')}{' '} { const output = lastFrame(); expect(output).toContain('Settings'); - expect(output).toContain('Apply To'); - expect(output).toContain('Use Enter to select, Tab to change focus'); + // Scope selector is now in a separate view (Tab to switch) + expect(output).not.toContain('Apply To'); + expect(output).toContain('(Use Enter to select, Tab to configure scope)'); }); it('should accept availableTerminalHeight prop without errors', () => { @@ -231,7 +232,7 @@ describe('SettingsDialog', () => { const output = lastFrame(); // Should still render properly with the height prop expect(output).toContain('Settings'); - expect(output).toContain('Use Enter to select'); + expect(output).toContain('Enter to select'); }); it('should show settings list with default values', () => { @@ -281,7 +282,7 @@ describe('SettingsDialog', () => { stdin.write(TerminalKeys.DOWN_ARROW as string); // Down arrow }); - expect(lastFrame()).toContain('● Disable Auto Update'); + expect(lastFrame()).toContain('● Language'); // The active index should have changed (tested indirectly through behavior) unmount(); @@ -342,7 +343,14 @@ describe('SettingsDialog', () => { await wait(); - expect(lastFrame()).toContain('● Vision Model Preview'); + const lastKey = getDialogSettingKeys().at(-1); + expect(lastKey).toBeDefined(); + + const lastLabel = lastKey + ? (getSettingDefinition(lastKey)?.label ?? lastKey) + : ''; + + expect(lastFrame()).toContain(`● ${lastLabel}`); unmount(); }); @@ -362,17 +370,21 @@ describe('SettingsDialog', () => { const { stdin, unmount, lastFrame } = render(component); - // Wait for initial render and verify we're on Vim Mode (first setting) + // Wait for initial render and verify we're on Tool Approval Mode (first setting) await waitFor(() => { - expect(lastFrame()).toContain('● Vim Mode'); + expect(lastFrame()).toContain('● Tool Approval Mode'); }); - // Navigate to Disable Auto Update setting and verify we're there + // Navigate to Vim Mode setting (third setting - a boolean) and verify we're there act(() => { - stdin.write(TerminalKeys.DOWN_ARROW as string); + stdin.write(TerminalKeys.DOWN_ARROW as string); // -> Language + }); + await wait(); + act(() => { + stdin.write(TerminalKeys.DOWN_ARROW as string); // -> Vim Mode }); await waitFor(() => { - expect(lastFrame()).toContain('● Disable Auto Update'); + expect(lastFrame()).toContain('● Vim Mode'); }); // Toggle the setting @@ -392,10 +404,10 @@ describe('SettingsDialog', () => { }); expect(vi.mocked(saveModifiedSettings)).toHaveBeenCalledWith( - new Set(['general.disableAutoUpdate']), + new Set(['general.vimMode']), { general: { - disableAutoUpdate: true, + vimMode: true, }, }, expect.any(LoadedSettings), @@ -406,51 +418,10 @@ describe('SettingsDialog', () => { }); describe('enum values', () => { - enum StringEnum { - FOO = 'foo', - BAR = 'bar', - BAZ = 'baz', - } - - const SETTING: SettingDefinition = { - type: 'enum', - label: 'Theme', - options: [ - { - label: 'Foo', - value: StringEnum.FOO, - }, - { - label: 'Bar', - value: StringEnum.BAR, - }, - { - label: 'Baz', - value: StringEnum.BAZ, - }, - ], - category: 'UI', - requiresRestart: false, - default: StringEnum.BAR, - description: 'The color theme for the UI.', - showInDialog: true, - }; - - const FAKE_SCHEMA: SettingsSchemaType = { - ui: { - showInDialog: false, - properties: { - theme: { - ...SETTING, - }, - }, - }, - } as unknown as SettingsSchemaType; - it('toggles enum values with the enter key', async () => { vi.mocked(saveModifiedSettings).mockClear(); - vi.mocked(getSettingsSchema).mockReturnValue(FAKE_SCHEMA); + // Use real schema - first setting "Tool Approval Mode" is an enum const settings = createMockSettings(); const onSelect = vi.fn(); const component = ( @@ -459,24 +430,30 @@ describe('SettingsDialog', () => { ); - const { stdin, unmount } = render(component); + const { stdin, unmount, lastFrame } = render(component); - // Press Enter to toggle current setting - stdin.write(TerminalKeys.DOWN_ARROW as string); - await wait(); - stdin.write(TerminalKeys.ENTER as string); + // Verify we're on Tool Approval Mode (first setting, an enum) + await waitFor(() => { + expect(lastFrame()).toContain('● Tool Approval Mode'); + }); + + // Press Enter to cycle the enum value + act(() => { + stdin.write(TerminalKeys.ENTER as string); + }); await wait(); await waitFor(() => { expect(vi.mocked(saveModifiedSettings)).toHaveBeenCalled(); }); + // Tool Approval Mode cycles through enum values expect(vi.mocked(saveModifiedSettings)).toHaveBeenCalledWith( - new Set(['ui.theme']), - { - ui: { - theme: StringEnum.BAZ, - }, - }, + new Set(['tools.approvalMode']), + expect.objectContaining({ + tools: expect.objectContaining({ + approvalMode: expect.any(String), + }), + }), expect.any(LoadedSettings), SettingScope.User, ); @@ -486,10 +463,10 @@ describe('SettingsDialog', () => { it('loops back when reaching the end of an enum', async () => { vi.mocked(saveModifiedSettings).mockClear(); - vi.mocked(getSettingsSchema).mockReturnValue(FAKE_SCHEMA); + // Use Tool Approval Mode set to YOLO (last value) to test looping back to first const settings = createMockSettings({ - ui: { - theme: StringEnum.BAZ, + tools: { + approvalMode: 'yolo', // Last enum value }, }); const onSelect = vi.fn(); @@ -499,24 +476,30 @@ describe('SettingsDialog', () => { ); - const { stdin, unmount } = render(component); + const { stdin, unmount, lastFrame } = render(component); - // Press Enter to toggle current setting - stdin.write(TerminalKeys.DOWN_ARROW as string); - await wait(); - stdin.write(TerminalKeys.ENTER as string); + // Verify we're on Tool Approval Mode (first setting) + await waitFor(() => { + expect(lastFrame()).toContain('● Tool Approval Mode'); + }); + + // Press Enter to cycle - should loop back to first value (Plan) + act(() => { + stdin.write(TerminalKeys.ENTER as string); + }); await wait(); await waitFor(() => { expect(vi.mocked(saveModifiedSettings)).toHaveBeenCalled(); }); + // Should loop back to first enum value (Plan) expect(vi.mocked(saveModifiedSettings)).toHaveBeenCalledWith( - new Set(['ui.theme']), - { - ui: { - theme: StringEnum.FOO, - }, - }, + new Set(['tools.approvalMode']), + expect.objectContaining({ + tools: expect.objectContaining({ + approvalMode: 'plan', // First enum value after YOLO + }), + }), expect.any(LoadedSettings), SettingScope.User, ); @@ -599,12 +582,12 @@ describe('SettingsDialog', () => { expect(lastFrame()).toContain('Vim Mode'); }); - // The UI should show the settings section is active and scope section is inactive - expect(lastFrame()).toContain('● Vim Mode'); // Settings section active - expect(lastFrame()).toContain(' Apply To'); // Scope section inactive + // The UI should show settings mode is active (scope is in separate view) + expect(lastFrame()).toContain('● Tool Approval Mode'); // Settings section active + expect(lastFrame()).not.toContain('Apply To'); // Scope is in a separate view - // This test validates the initial state - scope selection behavior - // is complex due to keypress handling, so we focus on state validation + // This test validates the initial state - scope selection is now + // accessed via Tab key, not shown alongside settings unmount(); }); @@ -668,12 +651,12 @@ describe('SettingsDialog', () => { // Wait for initial render await waitFor(() => { - expect(lastFrame()).toContain('Hide Window Title'); + expect(lastFrame()).toContain('Vim Mode'); }); - // Verify the dialog is rendered properly + // Verify the dialog is rendered properly (scope is in separate view) expect(lastFrame()).toContain('Settings'); - expect(lastFrame()).toContain('Apply To'); + expect(lastFrame()).not.toContain('Apply To'); // Scope is in a separate view // This test validates rendering - escape key behavior depends on complex // keypress handling that's difficult to test reliably in this environment @@ -1021,12 +1004,12 @@ describe('SettingsDialog', () => { expect(lastFrame()).toContain('Vim Mode'); }); - // Verify initial state: settings section active, scope section inactive - expect(lastFrame()).toContain('● Vim Mode'); // Settings section active - expect(lastFrame()).toContain(' Apply To'); // Scope section inactive + // Verify initial state: settings mode active (scope is in separate view) + expect(lastFrame()).toContain('● Tool Approval Mode'); // Settings mode active + expect(lastFrame()).not.toContain('Apply To'); // Scope is in a separate view // This test validates the rendered UI structure for tab navigation - // Actual tab behavior testing is complex due to keypress handling + // Tab now switches between settings view and scope view unmount(); }); @@ -1083,17 +1066,16 @@ describe('SettingsDialog', () => { expect(lastFrame()).toContain('Vim Mode'); }); - // Verify the complete UI is rendered with all necessary sections + // Verify the complete UI is rendered (scope is in separate view) expect(lastFrame()).toContain('Settings'); // Title - expect(lastFrame()).toContain('● Vim Mode'); // Active setting - expect(lastFrame()).toContain('Apply To'); // Scope section - expect(lastFrame()).toContain('User Settings'); // Scope options (no numbers when settings focused) + expect(lastFrame()).toContain('● Tool Approval Mode'); // Active setting + expect(lastFrame()).not.toContain('Apply To'); // Scope is in a separate view (Tab to access) expect(lastFrame()).toContain( - '(Use Enter to select, Tab to change focus)', + '(Use Enter to select, Tab to configure scope)', ); // Help text // This test validates the complete UI structure is available for user workflow - // Individual interactions are tested in focused unit tests + // Scope selection is now accessed via Tab key (view switching like ThemeDialog) unmount(); }); diff --git a/packages/cli/src/ui/components/SettingsDialog.tsx b/packages/cli/src/ui/components/SettingsDialog.tsx index 45b0f5548..8608fde7b 100644 --- a/packages/cli/src/ui/components/SettingsDialog.tsx +++ b/packages/cli/src/ui/components/SettingsDialog.tsx @@ -4,7 +4,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useState, useEffect } from 'react'; +import type React from 'react'; +import { useState, useEffect } from 'react'; import { Box, Text } from 'ink'; import { theme } from '../semantic-colors.js'; import type { LoadedSettings, Settings } from '../../config/settings.js'; @@ -57,10 +58,8 @@ export function SettingsDialog({ // Get vim mode context to sync vim mode changes const { vimEnabled, toggleVimEnabled } = useVimMode(); - // Focus state: 'settings' or 'scope' - const [focusSection, setFocusSection] = useState<'settings' | 'scope'>( - 'settings', - ); + // Mode state: 'settings' or 'scope' (view switching like ThemeDialog) + const [mode, setMode] = useState<'settings' | 'scope'>('settings'); // Scope selector state (User by default) const [selectedScope, setSelectedScope] = useState( SettingScope.User, @@ -105,7 +104,9 @@ export function SettingsDialog({ updated = setPendingSettingValue(key, value, updated); } else if ( (def?.type === 'number' && typeof value === 'number') || - (def?.type === 'string' && typeof value === 'string') + (def?.type === 'string' && typeof value === 'string') || + (def?.type === 'enum' && + (typeof value === 'string' || typeof value === 'number')) ) { updated = setPendingSettingValueAny(key, value, updated); } @@ -156,10 +157,6 @@ export function SettingsDialog({ ); } - setPendingSettings((prev) => - setPendingSettingValue(key, newValue as boolean, prev), - ); - if (!requiresRestart(key)) { const immediateSettings = new Set([key]); const immediateSettingsObject = setPendingSettingValueAny( @@ -381,15 +378,13 @@ export function SettingsDialog({ const handleScopeSelect = (scope: SettingScope) => { handleScopeHighlight(scope); - setFocusSection('settings'); + setMode('settings'); }; // Height constraint calculations similar to ThemeDialog const DIALOG_PADDING = 2; const SETTINGS_TITLE_HEIGHT = 2; // "Settings" title + spacing const SCROLL_ARROWS_HEIGHT = 2; // Up and down arrows - const SPACING_HEIGHT = 1; // Space between settings list and scope - const SCOPE_SELECTION_HEIGHT = 4; // Apply To section height const BOTTOM_HELP_TEXT_HEIGHT = 1; // Help text const RESTART_PROMPT_HEIGHT = showRestartPrompt ? 1 : 0; @@ -397,71 +392,28 @@ export function SettingsDialog({ availableTerminalHeight ?? Number.MAX_SAFE_INTEGER; currentAvailableTerminalHeight -= 2; // Top and bottom borders - // Start with basic fixed height (without scope selection) - let totalFixedHeight = + // Calculate fixed height (scope selection is now in a separate view, not included here) + const totalFixedHeight = DIALOG_PADDING + SETTINGS_TITLE_HEIGHT + SCROLL_ARROWS_HEIGHT + - SPACING_HEIGHT + BOTTOM_HELP_TEXT_HEIGHT + RESTART_PROMPT_HEIGHT; // Calculate how much space we have for settings - let availableHeightForSettings = Math.max( + const availableHeightForSettings = Math.max( 1, currentAvailableTerminalHeight - totalFixedHeight, ); - // Each setting item takes 2 lines (the setting row + spacing) - let maxVisibleItems = Math.max(1, Math.floor(availableHeightForSettings / 2)); - - // Decide whether to show scope selection based on remaining space - let showScopeSelection = true; - - // If we have limited height, prioritize showing more settings over scope selection - if (availableTerminalHeight && availableTerminalHeight < 25) { - // For very limited height, hide scope selection to show more settings - const totalWithScope = totalFixedHeight + SCOPE_SELECTION_HEIGHT; - const availableWithScope = Math.max( - 1, - currentAvailableTerminalHeight - totalWithScope, - ); - const maxItemsWithScope = Math.max(1, Math.floor(availableWithScope / 2)); - - // If hiding scope selection allows us to show significantly more settings, do it - if (maxVisibleItems > maxItemsWithScope + 1) { - showScopeSelection = false; - } else { - // Otherwise include scope selection and recalculate - totalFixedHeight += SCOPE_SELECTION_HEIGHT; - availableHeightForSettings = Math.max( - 1, - currentAvailableTerminalHeight - totalFixedHeight, - ); - maxVisibleItems = Math.max(1, Math.floor(availableHeightForSettings / 2)); - } - } else { - // For normal height, include scope selection - totalFixedHeight += SCOPE_SELECTION_HEIGHT; - availableHeightForSettings = Math.max( - 1, - currentAvailableTerminalHeight - totalFixedHeight, - ); - maxVisibleItems = Math.max(1, Math.floor(availableHeightForSettings / 2)); - } + // Each setting item takes 1 line + const maxVisibleItems = Math.max(1, availableHeightForSettings); // Use the calculated maxVisibleItems or fall back to the original maxItemsToShow const effectiveMaxItemsToShow = availableTerminalHeight ? Math.min(maxVisibleItems, items.length) : maxItemsToShow; - // Ensure focus stays on settings when scope selection is hidden - React.useEffect(() => { - if (!showScopeSelection && focusSection === 'scope') { - setFocusSection('settings'); - } - }, [showScopeSelection, focusSection]); - // Scroll logic for settings const visibleItems = items.slice( scrollOffset, @@ -474,10 +426,10 @@ export function SettingsDialog({ useKeypress( (key) => { const { name, ctrl } = key; - if (name === 'tab' && showScopeSelection) { - setFocusSection((prev) => (prev === 'settings' ? 'scope' : 'settings')); + if (name === 'tab') { + setMode((prev) => (prev === 'settings' ? 'scope' : 'settings')); } - if (focusSection === 'settings') { + if (mode === 'settings') { // If editing, capture input and control keys if (editingKey) { const definition = getSettingDefinition(editingKey); @@ -599,6 +551,18 @@ export function SettingsDialog({ } } else if (name === 'return' || name === 'space') { const currentItem = items[activeSettingIndex]; + if (currentItem?.value === 'ui.theme') { + if (name === 'return') { + onSelect('ui.theme', selectedScope); + } + return; + } + if (currentItem?.value === 'general.preferredEditor') { + if (name === 'return') { + onSelect('general.preferredEditor', selectedScope); + } + return; + } if ( currentItem?.type === 'number' || currentItem?.type === 'string' @@ -775,97 +739,95 @@ export function SettingsDialog({ - - - {focusSection === 'settings' ? '> ' : ' '} - {t('Settings')} - - - {showScrollUp && } - {visibleItems.map((item, idx) => { - const isActive = - focusSection === 'settings' && - activeSettingIndex === idx + scrollOffset; + {mode === 'settings' ? ( + + + {mode === 'settings' ? '> ' : ' '} + {t('Settings')} + + + {showScrollUp && } + {visibleItems.map((item, idx) => { + const isActive = + mode === 'settings' && activeSettingIndex === idx + scrollOffset; - const scopeSettings = settings.forScope(selectedScope).settings; - const mergedSettings = settings.merged; + const scopeSettings = settings.forScope(selectedScope).settings; + const mergedSettings = settings.merged; - let displayValue: string; - if (editingKey === item.value) { - // Show edit buffer with advanced cursor highlighting - if (cursorVisible && editCursorPos < cpLen(editBuffer)) { - // Cursor is in the middle or at start of text - const beforeCursor = cpSlice(editBuffer, 0, editCursorPos); - const atCursor = cpSlice( - editBuffer, - editCursorPos, - editCursorPos + 1, + let displayValue: string; + if (editingKey === item.value) { + // Show edit buffer with advanced cursor highlighting + if (cursorVisible && editCursorPos < cpLen(editBuffer)) { + // Cursor is in the middle or at start of text + const beforeCursor = cpSlice(editBuffer, 0, editCursorPos); + const atCursor = cpSlice( + editBuffer, + editCursorPos, + editCursorPos + 1, + ); + const afterCursor = cpSlice(editBuffer, editCursorPos + 1); + displayValue = + beforeCursor + chalk.inverse(atCursor) + afterCursor; + } else if (cursorVisible && editCursorPos >= cpLen(editBuffer)) { + // Cursor is at the end - show inverted space + displayValue = editBuffer + chalk.inverse(' '); + } else { + // Cursor not visible + displayValue = editBuffer; + } + } 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); + + const defaultValue = getDefaultValue(item.value); + + if (currentValue !== undefined && currentValue !== null) { + displayValue = String(currentValue); + } else { + displayValue = + defaultValue !== undefined && defaultValue !== null + ? String(defaultValue) + : ''; + } + + // Add * if value differs from default OR if currently being modified + const isModified = modifiedSettings.has(item.value); + const effectiveCurrentValue = + currentValue !== undefined && currentValue !== null + ? currentValue + : defaultValue; + const isDifferentFromDefault = + effectiveCurrentValue !== defaultValue; + + if (isDifferentFromDefault || isModified) { + displayValue += '*'; + } + } else { + // For booleans and other types, use existing logic + displayValue = getDisplayValue( + item.value, + scopeSettings, + mergedSettings, + modifiedSettings, + pendingSettings, ); - const afterCursor = cpSlice(editBuffer, editCursorPos + 1); - displayValue = - beforeCursor + chalk.inverse(atCursor) + afterCursor; - } else if (cursorVisible && editCursorPos >= cpLen(editBuffer)) { - // Cursor is at the end - show inverted space - displayValue = editBuffer + chalk.inverse(' '); - } else { - // Cursor not visible - displayValue = editBuffer; } - } 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); + const shouldBeGreyedOut = isDefaultValue(item.value, scopeSettings); - const defaultValue = getDefaultValue(item.value); - - if (currentValue !== undefined && currentValue !== null) { - displayValue = String(currentValue); - } else { - displayValue = - defaultValue !== undefined && defaultValue !== null - ? String(defaultValue) - : ''; - } - - // Add * if value differs from default OR if currently being modified - const isModified = modifiedSettings.has(item.value); - const effectiveCurrentValue = - currentValue !== undefined && currentValue !== null - ? currentValue - : defaultValue; - const isDifferentFromDefault = - effectiveCurrentValue !== defaultValue; - - if (isDifferentFromDefault || isModified) { - displayValue += '*'; - } - } else { - // For booleans and other types, use existing logic - displayValue = getDisplayValue( + // Generate scope message for this setting + const scopeMessage = getScopeMessageForSetting( item.value, - scopeSettings, - mergedSettings, - modifiedSettings, - pendingSettings, + selectedScope, + settings, ); - } - const shouldBeGreyedOut = isDefaultValue(item.value, scopeSettings); - // Generate scope message for this setting - const scopeMessage = getScopeMessageForSetting( - item.value, - selectedScope, - settings, - ); - - return ( - - + return ( + - - - ); - })} - {showScrollDown && } - - - - {/* Scope Selection - conditionally visible based on height constraints */} - {showScopeSelection && ( - - - - )} - - - - {t('(Use Enter to select{{tabText}})', { - tabText: showScopeSelection ? t(', Tab to change focus') : '', + ); })} + {showScrollDown && } + + ) : ( + + )} + + + {mode === 'settings' + ? t('(Use Enter to select, Tab to configure scope)') + : t('(Use Enter to apply scope, Tab to go back)')} - {showRestartPrompt && ( - - {t( - 'To see changes, Qwen Code must be restarted. Press r to exit and apply changes now.', - )} - - )} + {showRestartPrompt && ( + + {t( + 'To see changes, Qwen Code must be restarted. Press r to exit and apply changes now.', + )} + + )} ); } diff --git a/packages/cli/src/ui/components/ThemeDialog.tsx b/packages/cli/src/ui/components/ThemeDialog.tsx index 63e4fd647..3f93c84d7 100644 --- a/packages/cli/src/ui/components/ThemeDialog.tsx +++ b/packages/cli/src/ui/components/ThemeDialog.tsx @@ -278,7 +278,7 @@ def fibonacci(n): {mode === 'theme' ? t('(Use Enter to select, Tab to configure scope)') - : t('(Use Enter to apply scope, Tab to select theme)')} + : t('(Use Enter to apply scope, Tab to go back)')} diff --git a/packages/cli/src/ui/components/__snapshots__/SettingsDialog.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/SettingsDialog.test.tsx.snap index fbc2244b7..722751231 100644 --- a/packages/cli/src/ui/components/__snapshots__/SettingsDialog.test.tsx.snap +++ b/packages/cli/src/ui/components/__snapshots__/SettingsDialog.test.tsx.snap @@ -6,30 +6,17 @@ exports[`SettingsDialog > Snapshot Tests > should render default state correctly │ > Settings │ │ │ │ ▲ │ -│ ● Vim Mode false │ -│ │ -│ Disable Auto Update false │ -│ │ -│ Debug Keystroke Logging false │ -│ │ +│ ● Tool Approval Mode Default │ │ Language Auto (detect from system) │ -│ │ -│ Terminal Bell true │ -│ │ -│ Output Format Text │ -│ │ -│ Hide Window Title false │ -│ │ -│ Show Status in Title false │ -│ │ +│ Vim Mode false │ +│ Interactive Shell (PTY) false │ +│ Theme Qwen Dark │ +│ Preferred Editor │ +│ Auto-connect to IDE false │ +│ Show Line Numbers in Code false │ │ ▼ │ │ │ -│ │ -│ Apply To │ -│ ● User Settings │ -│ Workspace Settings │ -│ │ -│ (Use Enter to select, Tab to change focus) │ +│ (Use Enter to select, Tab to configure scope) │ │ │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯" `; @@ -40,30 +27,17 @@ exports[`SettingsDialog > Snapshot Tests > should render focused on scope select │ > Settings │ │ │ │ ▲ │ -│ ● Vim Mode false │ -│ │ -│ Disable Auto Update false │ -│ │ -│ Debug Keystroke Logging false │ -│ │ +│ ● Tool Approval Mode Default │ │ Language Auto (detect from system) │ -│ │ -│ Terminal Bell true │ -│ │ -│ Output Format Text │ -│ │ -│ Hide Window Title false │ -│ │ -│ Show Status in Title false │ -│ │ +│ Vim Mode false │ +│ Interactive Shell (PTY) false │ +│ Theme Qwen Dark │ +│ Preferred Editor │ +│ Auto-connect to IDE false │ +│ Show Line Numbers in Code false │ │ ▼ │ │ │ -│ │ -│ Apply To │ -│ ● User Settings │ -│ Workspace Settings │ -│ │ -│ (Use Enter to select, Tab to change focus) │ +│ (Use Enter to select, Tab to configure scope) │ │ │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯" `; @@ -74,30 +48,17 @@ exports[`SettingsDialog > Snapshot Tests > should render with accessibility sett │ > Settings │ │ │ │ ▲ │ -│ ● Vim Mode true* │ -│ │ -│ Disable Auto Update false │ -│ │ -│ Debug Keystroke Logging false │ -│ │ +│ ● Tool Approval Mode Default │ │ Language Auto (detect from system) │ -│ │ -│ Terminal Bell true │ -│ │ -│ Output Format Text │ -│ │ -│ Hide Window Title false │ -│ │ -│ Show Status in Title false │ -│ │ +│ Vim Mode true* │ +│ Interactive Shell (PTY) false │ +│ Theme Qwen Dark │ +│ Preferred Editor │ +│ Auto-connect to IDE false │ +│ Show Line Numbers in Code true* │ │ ▼ │ │ │ -│ │ -│ Apply To │ -│ ● User Settings │ -│ Workspace Settings │ -│ │ -│ (Use Enter to select, Tab to change focus) │ +│ (Use Enter to select, Tab to configure scope) │ │ │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯" `; @@ -108,30 +69,17 @@ exports[`SettingsDialog > Snapshot Tests > should render with all boolean settin │ > Settings │ │ │ │ ▲ │ -│ ● Vim Mode false* │ -│ │ -│ Disable Auto Update false* │ -│ │ -│ Debug Keystroke Logging false* │ -│ │ +│ ● Tool Approval Mode Default │ │ Language Auto (detect from system) │ -│ │ -│ Terminal Bell true │ -│ │ -│ Output Format Text │ -│ │ -│ Hide Window Title false* │ -│ │ -│ Show Status in Title false │ -│ │ +│ Vim Mode false* │ +│ Interactive Shell (PTY) false │ +│ Theme Qwen Dark │ +│ Preferred Editor │ +│ Auto-connect to IDE false* │ +│ Show Line Numbers in Code false* │ │ ▼ │ │ │ -│ │ -│ Apply To │ -│ ● User Settings │ -│ Workspace Settings │ -│ │ -│ (Use Enter to select, Tab to change focus) │ +│ (Use Enter to select, Tab to configure scope) │ │ │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯" `; @@ -142,30 +90,17 @@ exports[`SettingsDialog > Snapshot Tests > should render with different scope se │ > Settings │ │ │ │ ▲ │ -│ ● Vim Mode (Modified in System) false │ -│ │ -│ Disable Auto Update (Modified in System) false │ -│ │ -│ Debug Keystroke Logging false │ -│ │ +│ ● Tool Approval Mode Default │ │ Language Auto (detect from system) │ -│ │ -│ Terminal Bell true │ -│ │ -│ Output Format Text │ -│ │ -│ Hide Window Title false │ -│ │ -│ Show Status in Title false │ -│ │ +│ Vim Mode (Modified in System) false │ +│ Interactive Shell (PTY) false │ +│ Theme Qwen Dark │ +│ Preferred Editor │ +│ Auto-connect to IDE false │ +│ Show Line Numbers in Code false │ │ ▼ │ │ │ -│ │ -│ Apply To │ -│ ● User Settings │ -│ Workspace Settings │ -│ │ -│ (Use Enter to select, Tab to change focus) │ +│ (Use Enter to select, Tab to configure scope) │ │ │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯" `; @@ -176,30 +111,17 @@ exports[`SettingsDialog > Snapshot Tests > should render with different scope se │ > Settings │ │ │ │ ▲ │ -│ ● Vim Mode (Modified in Workspace) false │ -│ │ -│ Disable Auto Update false │ -│ │ -│ Debug Keystroke Logging (Modified in Workspace) false │ -│ │ +│ ● Tool Approval Mode Default │ │ Language Auto (detect from system) │ -│ │ -│ Terminal Bell true │ -│ │ -│ Output Format Text │ -│ │ -│ Hide Window Title false │ -│ │ -│ Show Status in Title false │ -│ │ +│ Vim Mode (Modified in Workspace) false │ +│ Interactive Shell (PTY) false │ +│ Theme Qwen Dark │ +│ Preferred Editor │ +│ Auto-connect to IDE false │ +│ Show Line Numbers in Code false │ │ ▼ │ │ │ -│ │ -│ Apply To │ -│ ● User Settings │ -│ Workspace Settings │ -│ │ -│ (Use Enter to select, Tab to change focus) │ +│ (Use Enter to select, Tab to configure scope) │ │ │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯" `; @@ -210,30 +132,17 @@ exports[`SettingsDialog > Snapshot Tests > should render with file filtering set │ > Settings │ │ │ │ ▲ │ -│ ● Vim Mode false │ -│ │ -│ Disable Auto Update false │ -│ │ -│ Debug Keystroke Logging false │ -│ │ +│ ● Tool Approval Mode Default │ │ Language Auto (detect from system) │ -│ │ -│ Terminal Bell true │ -│ │ -│ Output Format Text │ -│ │ -│ Hide Window Title false │ -│ │ -│ Show Status in Title false │ -│ │ +│ Vim Mode false │ +│ Interactive Shell (PTY) false │ +│ Theme Qwen Dark │ +│ Preferred Editor │ +│ Auto-connect to IDE false │ +│ Show Line Numbers in Code false │ │ ▼ │ │ │ -│ │ -│ Apply To │ -│ ● User Settings │ -│ Workspace Settings │ -│ │ -│ (Use Enter to select, Tab to change focus) │ +│ (Use Enter to select, Tab to configure scope) │ │ │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯" `; @@ -244,30 +153,17 @@ exports[`SettingsDialog > Snapshot Tests > should render with mixed boolean and │ > Settings │ │ │ │ ▲ │ -│ ● Vim Mode false* │ -│ │ -│ Disable Auto Update true* │ -│ │ -│ Debug Keystroke Logging false │ -│ │ +│ ● Tool Approval Mode Default │ │ Language Auto (detect from system) │ -│ │ -│ Terminal Bell true │ -│ │ -│ Output Format Text │ -│ │ -│ Hide Window Title false* │ -│ │ -│ Show Status in Title false │ -│ │ +│ Vim Mode false* │ +│ Interactive Shell (PTY) false │ +│ Theme Qwen Dark │ +│ Preferred Editor │ +│ Auto-connect to IDE false │ +│ Show Line Numbers in Code false │ │ ▼ │ │ │ -│ │ -│ Apply To │ -│ ● User Settings │ -│ Workspace Settings │ -│ │ -│ (Use Enter to select, Tab to change focus) │ +│ (Use Enter to select, Tab to configure scope) │ │ │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯" `; @@ -278,30 +174,17 @@ exports[`SettingsDialog > Snapshot Tests > should render with tools and security │ > Settings │ │ │ │ ▲ │ -│ ● Vim Mode false │ -│ │ -│ Disable Auto Update false │ -│ │ -│ Debug Keystroke Logging false │ -│ │ +│ ● Tool Approval Mode Default │ │ Language Auto (detect from system) │ -│ │ -│ Terminal Bell true │ -│ │ -│ Output Format Text │ -│ │ -│ Hide Window Title false │ -│ │ -│ Show Status in Title false │ -│ │ +│ Vim Mode false │ +│ Interactive Shell (PTY) false │ +│ Theme Qwen Dark │ +│ Preferred Editor │ +│ Auto-connect to IDE false │ +│ Show Line Numbers in Code false │ │ ▼ │ │ │ -│ │ -│ Apply To │ -│ ● User Settings │ -│ Workspace Settings │ -│ │ -│ (Use Enter to select, Tab to change focus) │ +│ (Use Enter to select, Tab to configure scope) │ │ │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯" `; @@ -312,30 +195,17 @@ exports[`SettingsDialog > Snapshot Tests > should render with various boolean se │ > Settings │ │ │ │ ▲ │ -│ ● Vim Mode true* │ -│ │ -│ Disable Auto Update true* │ -│ │ -│ Debug Keystroke Logging true* │ -│ │ +│ ● Tool Approval Mode Default │ │ Language Auto (detect from system) │ -│ │ -│ Terminal Bell true │ -│ │ -│ Output Format Text │ -│ │ -│ Hide Window Title true* │ -│ │ -│ Show Status in Title false │ -│ │ +│ Vim Mode true* │ +│ Interactive Shell (PTY) false │ +│ Theme Qwen Dark │ +│ Preferred Editor │ +│ Auto-connect to IDE true* │ +│ Show Line Numbers in Code true* │ │ ▼ │ │ │ -│ │ -│ Apply To │ -│ ● User Settings │ -│ Workspace Settings │ -│ │ -│ (Use Enter to select, Tab to change focus) │ +│ (Use Enter to select, Tab to configure scope) │ │ │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯" `; diff --git a/packages/cli/src/ui/components/__snapshots__/ThemeDialog.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/ThemeDialog.test.tsx.snap index 09787eca6..d254c32df 100644 --- a/packages/cli/src/ui/components/__snapshots__/ThemeDialog.test.tsx.snap +++ b/packages/cli/src/ui/components/__snapshots__/ThemeDialog.test.tsx.snap @@ -4,10 +4,11 @@ exports[`ThemeDialog Snapshots > should render correctly in scope selector mode "╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ > Apply To │ +│ │ │ ● 1. User Settings │ │ 2. Workspace Settings │ │ │ -│ (Use Enter to apply scope, Tab to select theme) │ +│ (Use Enter to apply scope, Tab to go back) │ │ │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯" `; diff --git a/packages/cli/src/ui/components/shared/ScopeSelector.tsx b/packages/cli/src/ui/components/shared/ScopeSelector.tsx index 04ff8080d..f5d265bfd 100644 --- a/packages/cli/src/ui/components/shared/ScopeSelector.tsx +++ b/packages/cli/src/ui/components/shared/ScopeSelector.tsx @@ -45,6 +45,7 @@ export function ScopeSelector({ {isFocused ? '> ' : ' '} {t('Apply To')} + void; + openEditorDialog: () => void; handleThemeSelect: ( themeName: string | undefined, scope: SettingScope, diff --git a/packages/cli/src/utils/settingsUtils.test.ts b/packages/cli/src/utils/settingsUtils.test.ts index ca1dc802c..109197be3 100644 --- a/packages/cli/src/utils/settingsUtils.test.ts +++ b/packages/cli/src/utils/settingsUtils.test.ts @@ -110,6 +110,7 @@ describe('SettingsUtils', () => { category: 'UI', default: false, requiresRestart: true, + showInDialog: true, }, accessibility: { type: 'object', diff --git a/packages/cli/src/utils/settingsUtils.ts b/packages/cli/src/utils/settingsUtils.ts index dcc3f2b6c..a17cb6bcf 100644 --- a/packages/cli/src/utils/settingsUtils.ts +++ b/packages/cli/src/utils/settingsUtils.ts @@ -249,12 +249,76 @@ export function getDialogSettingsByType( } /** - * Get all setting keys that should be shown in the dialog + * Explicit display order for settings shown in the Settings Dialog. + * Settings are ordered by importance and logical grouping: + * 1. Workflow control (most impactful) + * 2. Localization + * 3. Editor/Shell experience + * 4. Display preferences + * 5. Git behavior + * 6. File filtering + * 7. System settings (rarely changed) + * + * New settings with showInDialog: true that are not listed here + * will appear at the end of the list. + */ +const SETTINGS_DIALOG_ORDER: readonly string[] = [ + // Workflow Control - most impactful setting + 'tools.approvalMode', + + // Localization - users often set this first + 'general.language', + + // Editor/Shell Experience + 'general.vimMode', + 'tools.shell.enableInteractiveShell', + + // Display Preferences + 'ui.theme', + 'general.preferredEditor', + 'ide.enabled', + 'ui.showLineNumbers', + 'ui.hideTips', + 'general.terminalBell', + 'ui.enableWelcomeBack', + + // Git Behavior + 'general.gitCoAuthor', + + // File Filtering + 'context.fileFiltering.respectGitIgnore', + 'context.fileFiltering.respectQwenIgnore', + + // System Settings - rarely changed + 'general.disableAutoUpdate', + + // Privacy + 'privacy.usageStatisticsEnabled', +] as const; + +/** + * Get all setting keys that should be shown in the dialog, sorted by display order */ export function getDialogSettingKeys(): string[] { - return Object.values(getFlattenedSchema()) - .filter((definition) => definition.showInDialog !== false) + const dialogSettings = Object.values(getFlattenedSchema()) + .filter((definition) => definition.showInDialog === true) .map((definition) => definition.key); + + // Sort by explicit order; settings not in the order array appear at the end + return dialogSettings.sort((a, b) => { + const indexA = SETTINGS_DIALOG_ORDER.indexOf(a); + const indexB = SETTINGS_DIALOG_ORDER.indexOf(b); + + // If both are in the order array, sort by their position + if (indexA !== -1 && indexB !== -1) { + return indexA - indexB; + } + // If only one is in the array, prioritize the one in the array + if (indexA !== -1) return -1; + if (indexB !== -1) return 1; + // If neither is in the array, maintain original order + return 0; + }); } // ============================================================================