diff --git a/src/renderer/src/assets/terminal.css b/src/renderer/src/assets/terminal.css index 3496db1fc7e..e7b5afe5030 100644 --- a/src/renderer/src/assets/terminal.css +++ b/src/renderer/src/assets/terminal.css @@ -31,6 +31,12 @@ overflow-y: scroll; } +/* Round the xterm DOM scrollbar thumb; xterm has no option for this, and its + default .xterm-slider has square corners. */ +.xterm .xterm-scrollable-element > .xterm-scrollbar > .xterm-slider { + border-radius: 4px; +} + /* xterm.css sets position:absolute;top:0 on .xterm-helpers but omits left, leaving left:auto. In Electron's Blink, left:auto can resolve to a non-zero value and offset the IME candidate window away from the cursor. Anchoring it diff --git a/src/renderer/src/components/terminal-pane/compose-active-terminal-theme.test.ts b/src/renderer/src/components/terminal-pane/compose-active-terminal-theme.test.ts index 4e493598a47..97e81b97135 100644 --- a/src/renderer/src/components/terminal-pane/compose-active-terminal-theme.test.ts +++ b/src/renderer/src/components/terminal-pane/compose-active-terminal-theme.test.ts @@ -12,10 +12,30 @@ describe('composeActiveTerminalTheme', () => { } as GlobalSettings } - it('returns the base theme when no overrides apply', () => { + it('layers terminal scrollbar defaults under the base theme', () => { const base = { background: '#101010', foreground: '#fafafa', cursor: '#fafafa' } const result = composeActiveTerminalTheme(base, settingsWith({})) - expect(result).toEqual(base) + expect(result).toEqual({ + overviewRulerBorder: 'transparent', + scrollbarSliderBackground: 'rgba(180, 180, 185, 0.4)', + scrollbarSliderHoverBackground: 'rgba(180, 180, 185, 0.6)', + scrollbarSliderActiveBackground: 'rgba(180, 180, 185, 0.8)', + ...base + }) + }) + + it('lets the base theme override terminal scrollbar defaults', () => { + const result = composeActiveTerminalTheme( + { + background: '#101010', + overviewRulerBorder: '#222222', + scrollbarSliderBackground: 'rgba(1, 2, 3, 0.4)' + }, + settingsWith({}) + ) + + expect(result!.overviewRulerBorder).toBe('#222222') + expect(result!.scrollbarSliderBackground).toBe('rgba(1, 2, 3, 0.4)') }) it('layers terminalColorOverrides on top of the base theme', () => { diff --git a/src/renderer/src/components/terminal-pane/terminal-appearance.ts b/src/renderer/src/components/terminal-pane/terminal-appearance.ts index dafbb562bee..12e3bace3cb 100644 --- a/src/renderer/src/components/terminal-pane/terminal-appearance.ts +++ b/src/renderer/src/components/terminal-pane/terminal-appearance.ts @@ -156,7 +156,19 @@ export function composeActiveTerminalTheme( if (!baseTheme) { return null } - let theme: ITheme = { ...baseTheme } + // Why: setting scrollbar.width enables xterm's overview ruler, whose border + // defaults to the foreground color and paints a bright vertical line beside + // the scrollbar. We only want the slimmer scrollbar, not the ruler chrome. + // Why: xterm's default slider alpha (~0.2) is nearly invisible on dark + // backgrounds; raise the contrast so the thumb reads. Placed before the + // spread so an explicit theme value still wins. + let theme: ITheme = { + overviewRulerBorder: 'transparent', + scrollbarSliderBackground: 'rgba(180, 180, 185, 0.4)', + scrollbarSliderHoverBackground: 'rgba(180, 180, 185, 0.6)', + scrollbarSliderActiveBackground: 'rgba(180, 180, 185, 0.8)', + ...baseTheme + } // Why: merge user-imported Ghostty color overrides on top of the resolved // base theme so individual colors can be tweaked without losing the rest. if (settings.terminalColorOverrides) { diff --git a/src/renderer/src/lib/pane-manager/pane-lifecycle.test.ts b/src/renderer/src/lib/pane-manager/pane-lifecycle.test.ts index 825e5709562..d926a7707b9 100644 --- a/src/renderer/src/lib/pane-manager/pane-lifecycle.test.ts +++ b/src/renderer/src/lib/pane-manager/pane-lifecycle.test.ts @@ -87,6 +87,10 @@ describe('buildDefaultTerminalOptions', () => { // silently breaks enhanced chords, especially inside tmux. expect(buildDefaultTerminalOptions().vtExtensions?.kittyKeyboard).toBe(true) }) + + it('uses xterm scrollbar options for the slimmer terminal scrollbar', () => { + expect(buildDefaultTerminalOptions().scrollbar?.width).toBe(7) + }) }) describe('attachWebgl', () => { diff --git a/src/renderer/src/lib/pane-manager/pane-terminal-options.ts b/src/renderer/src/lib/pane-manager/pane-terminal-options.ts index 39bf07c6bab..814b732972c 100644 --- a/src/renderer/src/lib/pane-manager/pane-terminal-options.ts +++ b/src/renderer/src/lib/pane-manager/pane-terminal-options.ts @@ -26,6 +26,13 @@ export function buildDefaultTerminalOptions(): ITerminalOptions { fontWeight: '300', fontWeightBold: '500', scrollback: 10000, + // Why: xterm 6.x renders its own DOM scrollbar (default 14px), which no CSS + // on .xterm-viewport can resize. Slim it via the option. Setting width also + // enables the overview ruler, whose border is hidden via overviewRulerBorder + // in composeActiveTerminalTheme. + scrollbar: { + width: 7 + }, allowTransparency: false, // Why: on macOS, non-US layouts rely on Option to compose characters like @ and €. macOptionIsMeta: false,