fix(settings): show the default word separators as an editable value (#28336)

Double-click selected across spaces when a custom Word Separators value
omitted the space. The field was empty with the default (which starts with
an invisible space) only as a placeholder, so users rebuilt the list
without it. Like xterm.js, VS Code and Ghostty, Orca keeps respecting the
configured value exactly; the field now shows the default as its value,
offers Reset when customized, and warns when no space is included.

Fixes #28335

Co-authored-by: Neil <neil@stably.ai>
This commit is contained in:
hnkƶ
2026-10-28 01:50:15 -07:00
committed by GitHub
co-authored by Neil
parent 55b26f814b
commit 86f9b369d8
12 changed files with 82 additions and 26 deletions
@@ -3,14 +3,16 @@
import { act } from 'react'
import { createRoot, type Root } from 'react-dom/client'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { getDefaultSettings } from '../../../../shared/constants'
import type { GlobalSettings } from '../../../../shared/global-settings-types'
import { TERMINAL_DEFAULT_WORD_SEPARATOR } from '../../../../shared/terminal-word-separator'
import { TerminalAdvancedSection } from './TerminalAdvancedSection'
vi.mock('@/i18n/i18n', () => ({
translate: (_key: string, defaultValue: string) => defaultValue
}))
describe('TerminalAdvancedSection scrollback rows', () => {
describe('TerminalAdvancedSection', () => {
let container: HTMLDivElement
let root: Root
@@ -26,11 +28,11 @@ describe('TerminalAdvancedSection scrollback rows', () => {
document.body.replaceChildren()
})
function renderSection(updateSettings = vi.fn()): void {
function renderSection(updateSettings = vi.fn(), overrides: Partial<GlobalSettings> = {}): void {
act(() => {
root.render(
<TerminalAdvancedSection
settings={{ terminalScrollbackRows: 5000 } as GlobalSettings}
settings={{ ...getDefaultSettings('/tmp'), terminalScrollbackRows: 5000, ...overrides }}
updateSettings={updateSettings}
scrollbackMode="custom"
setScrollbackMode={vi.fn()}
@@ -103,4 +105,23 @@ describe('TerminalAdvancedSection scrollback rows', () => {
expect(updateSettings).toHaveBeenCalledWith({ terminalScrollbackRows: 12345 })
expect(input.value).toBe('12345')
})
it('shows the no-space hint for a custom separator and resets to the default', () => {
const updateSettings = vi.fn()
renderSection(updateSettings, { terminalWordSeparator: '()[]' })
const input = container.querySelector<HTMLInputElement>('input:not([type])')
expect(input?.value).toBe('()[]')
expect(container.textContent).toContain('No space included')
const reset = [...container.querySelectorAll('button')].find((b) => b.textContent === 'Reset')
act(() => reset?.click())
expect(updateSettings).toHaveBeenCalledWith({ terminalWordSeparator: undefined })
expect(input?.value).toBe(TERMINAL_DEFAULT_WORD_SEPARATOR)
expect(container.textContent).not.toContain('No space included')
expect([...container.querySelectorAll('button')].some((b) => b.textContent === 'Reset')).toBe(
false
)
})
})
@@ -6,6 +6,7 @@ import {
DESKTOP_TERMINAL_SCROLLBACK_ROWS_MIN,
normalizeDesktopTerminalScrollbackRows
} from '../../../../shared/terminal-scrollback-policy'
import { Button } from '../ui/button'
import { Input } from '../ui/input'
import { ToggleGroup, ToggleGroupItem } from '../ui/toggle-group'
import {
@@ -35,6 +36,10 @@ function formatScrollbackRowsLabel(rows: number): string {
return rows % 1_000 === 0 ? `${rows / 1_000}k` : String(rows)
}
function toWordSeparatorSetting(draft: string): string | undefined {
return draft && draft !== TERMINAL_DEFAULT_WORD_SEPARATOR ? draft : undefined
}
export function TerminalAdvancedSection({
settings,
updateSettings,
@@ -58,6 +63,22 @@ export function TerminalAdvancedSection({
)
const scrollbackToggleValue =
scrollbackMode === 'custom' ? 'custom' : isPreset ? `${scrollbackRows}` : 'custom'
const wordSeparator = settings.terminalWordSeparator
const [wordSeparatorDraft, setWordSeparatorDraft] = useState(
wordSeparator ?? TERMINAL_DEFAULT_WORD_SEPARATOR
)
const [prevWordSeparator, setPrevWordSeparator] = useState(wordSeparator)
if (wordSeparator !== prevWordSeparator) {
setPrevWordSeparator(wordSeparator)
// Why: an emptied field stays empty while editing instead of snapping back to the default.
if (wordSeparator !== toWordSeparatorSetting(wordSeparatorDraft)) {
setWordSeparatorDraft(wordSeparator ?? TERMINAL_DEFAULT_WORD_SEPARATOR)
}
}
const commitWordSeparatorDraft = (draft: string): void => {
setWordSeparatorDraft(draft)
updateSettings({ terminalWordSeparator: toWordSeparatorSetting(draft) })
}
const powerShellImplementation = settings.terminalWindowsPowerShellImplementation ?? 'auto'
const commitScrollbackRowsDraft = (): void => {
const trimmed = scrollbackRowsDraft.trim()
@@ -176,27 +197,47 @@ export function TerminalAdvancedSection({
<SearchableSetting
title={translate('auto.components.settings.TerminalPane.4bebcc2b2c', 'Word Separators')}
description={translate(
'auto.components.settings.TerminalPane.8a956cc91e',
'Characters treated as word boundaries for double-click selection.'
'settings.terminal.wordSeparators.description',
'Characters treated as word boundaries for double-click selection, including space.'
)}
keywords={['word', 'separator', 'boundary', 'double-click', 'selection']}
>
<SettingsRow
label={translate('auto.components.settings.TerminalPane.4bebcc2b2c', 'Word Separators')}
description={translate(
'auto.components.settings.TerminalPane.8a956cc91e',
'Characters treated as word boundaries for double-click selection.'
'settings.terminal.wordSeparators.description',
'Characters treated as word boundaries for double-click selection, including space.'
)}
control={
<Input
value={settings.terminalWordSeparator ?? ''}
onChange={(e) => {
const value = e.target.value
updateSettings({ terminalWordSeparator: value || undefined })
}}
placeholder={TERMINAL_DEFAULT_WORD_SEPARATOR}
className="w-56 font-mono text-xs"
/>
<div className="flex w-56 flex-col items-end gap-1">
<div className="flex w-full items-center gap-1">
<Input
value={wordSeparatorDraft}
onChange={(e) => commitWordSeparatorDraft(e.target.value)}
variant="code"
spellCheck={false}
className="min-w-0 flex-1"
/>
{wordSeparatorDraft !== TERMINAL_DEFAULT_WORD_SEPARATOR ? (
<Button
type="button"
variant="ghost"
size="xs"
onClick={() => commitWordSeparatorDraft(TERMINAL_DEFAULT_WORD_SEPARATOR)}
>
{translate('auto.components.settings.AgentsPane.5200dac9da', 'Reset')}
</Button>
) : null}
</div>
{wordSeparatorDraft && !wordSeparatorDraft.includes(' ') ? (
<p className="text-xs text-status-warning">
{translate(
'settings.terminal.wordSeparators.noSpaceHint',
'No space included — double-click will select across spaces.'
)}
</p>
) : null}
</div>
}
/>
</SearchableSetting>
+4 -1
View File
@@ -363,6 +363,10 @@
"openManageSessions": "Open Manage Sessions",
"administratorSearchDescription": "See Orca’s Windows permissions or restart with administrator approval.",
"administratorApproval": "Windows will ask for approval. Use your usual account."
},
"wordSeparators": {
"description": "Characters treated as word boundaries for double-click selection, including space.",
"noSpaceHint": "No space included — double-click will select across spaces."
}
},
"appearance": {
@@ -8592,7 +8596,6 @@
"a016ffbeed": "Auto uses Windows PowerShell now and switches to PowerShell 7+ when installed.",
"5ed5c95344": "Choose between Windows PowerShell and PowerShell 7+ for new terminal panes.",
"3d88af864d": "Choose whether the PowerShell shell option launches Windows PowerShell or PowerShell 7+ for new terminal panes.",
"8a956cc91e": "Characters treated as word boundaries for double-click selection.",
"4bebcc2b2c": "Word Separators",
"12e06178fa": "rows",
"907b0b9d3e": "Custom",
-1
View File
@@ -8220,7 +8220,6 @@
"a016ffbeed": "Auto usa Windows PowerShell ahora y cambia a PowerShell 7+ cuando está instalado.",
"5ed5c95344": "Elige entre Windows PowerShell y PowerShell 7+ para nuevos paneles de terminal.",
"3d88af864d": "Elige si la opción de shell PowerShell inicia Windows PowerShell o PowerShell 7+ para nuevos paneles de terminal.",
"8a956cc91e": "Caracteres tratados como límites de palabras para la selección con doble clic.",
"4bebcc2b2c": "Separadores de palabras",
"12e06178fa": "filas",
"907b0b9d3e": "Personalizado",
-1
View File
@@ -8439,7 +8439,6 @@
"a016ffbeed": "Auto utilise Windows PowerShell pour le moment et bascule vers PowerShell 7+ une fois installé.",
"5ed5c95344": "Choisissez entre Windows PowerShell et PowerShell 7+ pour les nouveaux volets de terminal.",
"3d88af864d": "Choisissez si l'option de shell PowerShell lance Windows PowerShell ou PowerShell 7+ pour les nouveaux volets de terminal.",
"8a956cc91e": "Caractères traités comme séparateurs de mots pour la sélection par double-clic.",
"4bebcc2b2c": "Séparateurs de mots",
"12e06178fa": "lignes",
"907b0b9d3e": "Personnalisé",
-1
View File
@@ -8226,7 +8226,6 @@
"a016ffbeed": "Auto は現在 Windows PowerShell を使用し、インストールされると PowerShell 7 以降に切り替わります。",
"5ed5c95344": "新規ターミナルペインについては、Windows PowerShell と PowerShell 7+ のどちらかを選択します。",
"3d88af864d": "PowerShell シェルオプションで、新規ターミナルペインに対して Windows PowerShell を起動するか PowerShell 7+ を起動するかを選択します。",
"8a956cc91e": "文字はダブルクリック選択の単語境界として扱われます。",
"4bebcc2b2c": "単語の区切り文字",
"12e06178fa": "行",
"907b0b9d3e": "カスタム",
-1
View File
@@ -8189,7 +8189,6 @@
"a016ffbeed": "자동은 현재 Windows PowerShell을 사용하고, PowerShell 7+가 설치되면 전환합니다.",
"5ed5c95344": "새 터미널 패널을 위해 Windows PowerShell과 PowerShell 7+ 중에서 선택하세요.",
"3d88af864d": "PowerShell 셸 옵션이 새 터미널 패널에 대해 Windows PowerShell을 시작할지 아니면 PowerShell 7+를 시작할지 선택합니다.",
"8a956cc91e": "두 번 클릭 선택 시 단어 경계로 처리되는 문자입니다.",
"4bebcc2b2c": "단어 구분 기호",
"12e06178fa": "행",
"907b0b9d3e": "사용자 지정",
-1
View File
@@ -4023,7 +4023,6 @@
"a016ffbeed": "Auto usa o Windows PowerShell agora e alterna para o PowerShell 7+ quando instalado.",
"5ed5c95344": "Escolha entre Windows PowerShell e PowerShell 7+ para novos painéis de terminal.",
"3d88af864d": "Escolha se a opção de shell PowerShell inicia o Windows PowerShell ou o PowerShell 7+ para novos painéis de terminal.",
"8a956cc91e": "Caracteres tratados como limites de palavra para seleção por clique duplo.",
"4bebcc2b2c": "Separadores de Palavra",
"12e06178fa": "linhas",
"907b0b9d3e": "Personalizado",
-1
View File
@@ -8616,7 +8616,6 @@
"a016ffbeed": "Сейчас «Авто» использует Windows PowerShell и переключится на PowerShell 7+ после установки.",
"5ed5c95344": "Выберите Windows PowerShell или PowerShell 7+ для новых панелей терминала.",
"3d88af864d": "Выберите, какую оболочку запускает пункт PowerShell для новых панелей терминала: Windows PowerShell или PowerShell 7+.",
"8a956cc91e": "Символы границ слов для выделения двойным щелчком.",
"4bebcc2b2c": "Разделители слов",
"12e06178fa": "строк",
"907b0b9d3e": "Пользовательские",
-1
View File
@@ -8556,7 +8556,6 @@
"a016ffbeed": "Otomatik, şu anda Windows PowerShell'i kullanır ve kurulduğunda PowerShell 7+'a geçer.",
"5ed5c95344": "Yeni terminal bölmeleri için Windows PowerShell ve PowerShell 7+ arasında seçim yapın.",
"3d88af864d": "PowerShell kabuk seçeneğinin yeni terminal bölmeleri için Windows PowerShell mi yoksa PowerShell 7+ mı başlatacağını seçin.",
"8a956cc91e": "Çift tıklama seçiminde kelime sınırı olarak kabul edilen karakterler.",
"4bebcc2b2c": "Kelime Ayırıcıları",
"12e06178fa": "satır",
"907b0b9d3e": "Özel",
@@ -8545,7 +8545,6 @@
"a016ffbeed": "「自動」模式目前使用 Windows PowerShell,並在安裝後切換到 PowerShell 7+。",
"5ed5c95344": "為新的終端機窗格選擇 Windows PowerShell 或 PowerShell 7+。",
"3d88af864d": "使用 PowerShell shell 選項開啟新的終端機窗格時,選擇要啟動 Windows PowerShell 還是 PowerShell 7+。",
"8a956cc91e": "按兩下選取時視為字詞邊界的字元。",
"4bebcc2b2c": "字詞分隔字元",
"12e06178fa": "列",
"907b0b9d3e": "自訂",
-1
View File
@@ -8189,7 +8189,6 @@
"a016ffbeed": "Auto 现在使用 Windows PowerShell,并在安装后切换到 PowerShell 7+。",
"5ed5c95344": "为新的终端窗格选择 Windows PowerShell 和 PowerShell 7+。",
"3d88af864d": "选择 PowerShell shell 选项是为新终端窗格启动 Windows PowerShell 还是 PowerShell 7+。",
"8a956cc91e": "双击选择时将字符视为单词边界。",
"4bebcc2b2c": "单词分隔符",
"12e06178fa": "行",
"907b0b9d3e": "自定义",