diff --git a/docs/reference/plugin-theme-tokens.md b/docs/reference/plugin-theme-tokens.md index f95afd04ce9..790524142ce 100644 --- a/docs/reference/plugin-theme-tokens.md +++ b/docs/reference/plugin-theme-tokens.md @@ -64,6 +64,7 @@ Every key an app theme may set in `tokens` (see [plugin themes](plugin-themes.md | `--chat-selection` | Selected text in chat; may be translucent, it paints over the chat | no | `--text-link` (else Orca's link blue) mixed into the chat background, up to 30% light and 40% dark, as deep as keeps selected text 4.5:1 (never under 15%) | | `--chat-selection-foreground` | Text inside a chat selection | no | same as `--foreground` | | `--text-link` | Links in Markdown previews, editors and agent chat | no | Orca's built-in color | +| `--editor-selection` | Selected text in the code editor; keep it translucent so find matches show through. An unfocused editor shows it at half its opacity | no | `--ring` at 30% opacity | | `--diff-added` | Added-line hue: Monaco overview ruler and chat diff text | no | same as `--git-decoration-added` | | `--diff-added-background` | Added lines in diffs | no | light: `--diff-added` at 13% opacity; dark: `--diff-added` at 16% opacity | | `--diff-added-emphasis` | Added characters within a changed line | no | light: `--diff-added` at 15% opacity; dark: `--diff-added` at 20% opacity | diff --git a/docs/reference/plugin-themes.md b/docs/reference/plugin-themes.md index 71a97aff8db..cf4709f520d 100644 --- a/docs/reference/plugin-themes.md +++ b/docs/reference/plugin-themes.md @@ -33,13 +33,14 @@ file of at most 16 KiB, or 64 KiB for an icon theme. `--primary`/`--primary-foreground`) must be opaque with at least 4.5:1 contrast; a pair that fails is dropped and keeps Orca's built-in colors. A pair you set only half of is checked against what the other half falls back to. Some checks drop only your token: `--text-link` - needs 4.5:1 on `--editor-surface`, each `--git-decoration-*` 3:1 on `--sidebar`, - `--chat-bold-foreground` 4.5:1 on the chat background and on `--chat-user-message`, - `--chat-muted-foreground` 4.5:1 on the chat background and on code blocks, and + needs 4.5:1 on `--editor-surface`, `--editor-selection` 4.5:1 under `--foreground`, each + `--git-decoration-*` 3:1 on `--sidebar`, `--chat-bold-foreground` 4.5:1 on the chat + background and on `--chat-user-message`, `--chat-muted-foreground` 4.5:1 on the chat + background and on code blocks, and `--worktree-sidebar-active`, `--chat-user-message`, `--chat-inline-code` (against the chat link color), `--chat-code-block` and the diff line and character tints 4.5:1 with the text drawn on - them. Those surfaces, `--chat-inline-code`, `--chat-selection` and `--host-badge` may be - translucent; they are checked as painted over the surface beneath. + them. Those surfaces, `--chat-inline-code`, `--chat-selection`, `--editor-selection` and + `--host-badge` may be translucent; they are checked as painted over the surface beneath. - An omitted surface token paints exactly what Orca paints without a theme. The code editor's diff view and links keep Monaco's own colors unless the theme sets a diff token or `--text-link`. Setting only `--diff-added` and `--diff-removed` is enough; the line, gutter and @@ -57,8 +58,9 @@ An app theme also colors the code editor: its background, text, line numbers, cu selection follow the theme's tokens. Users can also pick any app theme, of either base, as just their editor theme (Settings → Appearance → Light/Dark editor theme); the editor then takes its colors from `--editor-surface` (else `--background`), `--foreground`, `--muted-foreground`, -`--accent`, `--ring`, `--popover`, `--border` and `tokenColors`, while the rest of Orca keeps the -app theme. If those tokens can't make a legible editor, it uses Monaco's default colors. To +`--accent`, `--ring`, `--editor-selection`, `--popover`, `--border` and `tokenColors`, while the +rest of Orca keeps the app theme. Keep `--editor-selection` translucent so find matches and +highlighted words show through it; without it the selection is `--ring` at 30%. If those tokens can't make a legible editor, it uses Monaco's default colors. To color code itself, add `tokenColors` (Orca 1.4.245 and later) in the same shape as a VS Code theme's, so most VS Code theme lists paste in unchanged (`validate:plugin` flags entries without a `scope` and any setting other than these two): ```json diff --git a/src/renderer/src/app-shell/appearance/plugin-theme-contrast-rules.ts b/src/renderer/src/app-shell/appearance/plugin-theme-contrast-rules.ts index 8f6f1972b26..af7e522d12f 100644 --- a/src/renderer/src/app-shell/appearance/plugin-theme-contrast-rules.ts +++ b/src/renderer/src/app-shell/appearance/plugin-theme-contrast-rules.ts @@ -119,6 +119,11 @@ const CONTRAST_RULE_DEFINITIONS: readonly ContrastRule[] = [ ['--chat-inline-code', '--chat-user-message'] ].map((under) => chatPair('--chat-selection', '--chat-selection-foreground', undefined, under)), { ...textPair('--editor-surface', '--text-link'), drop: ['--text-link'] }, + { + ...textPair('--editor-selection', '--foreground'), + drop: ['--editor-selection'], + over: '--editor-surface' + }, chatPair('--chat-user-message', '--chat-link', ['--chat-user-message']), ...GIT_DECORATIONS.map((status): ContrastRule => ({ surface: '--sidebar', diff --git a/src/renderer/src/lib/editor/appearance/plugin-monaco-theme.test.ts b/src/renderer/src/lib/editor/appearance/plugin-monaco-theme.test.ts new file mode 100644 index 00000000000..00b745b6220 --- /dev/null +++ b/src/renderer/src/lib/editor/appearance/plugin-monaco-theme.test.ts @@ -0,0 +1,29 @@ +import { expect, it } from 'vitest' +import type { ColorProbe } from '../../../app-shell/appearance/plugin-theme-tokens' +import { derivePluginMonacoTheme } from './plugin-monaco-theme' + +const probe: ColorProbe = (value) => { + const hex = /^#([0-9a-f]{6})([0-9a-f]{2})?$/i.exec(value.trim()) + if (!hex) { + return null + } + const channel = (offset: number): number => Number.parseInt(hex[1].slice(offset, offset + 2), 16) + return [channel(0), channel(2), channel(4), hex[2] ? Number.parseInt(hex[2], 16) / 255 : 1] +} + +it('paints a theme-chosen editor selection instead of the ring', () => { + const tokens: Record = { + '--background': '#261d45', + '--foreground': '#ffffff', + '--ring': '#00ff6a', + '--editor-selection': '#ff2a6d66' + } + const theme = derivePluginMonacoTheme({ + base: 'dark', + read: (token) => tokens[token] ?? '', + painted: tokens, + probe + }) + expect(theme?.data.colors['editor.selectionBackground']).toBe('#ff2a6d66') + expect(theme?.data.colors['editor.inactiveSelectionBackground']).toBe('#ff2a6d33') +}) diff --git a/src/renderer/src/lib/editor/appearance/plugin-monaco-theme.ts b/src/renderer/src/lib/editor/appearance/plugin-monaco-theme.ts index db718cb3ea2..80ecb2637fb 100644 --- a/src/renderer/src/lib/editor/appearance/plugin-monaco-theme.ts +++ b/src/renderer/src/lib/editor/appearance/plugin-monaco-theme.ts @@ -84,6 +84,9 @@ export function derivePluginMonacoTheme(input: { const muted = color('--muted-foreground') ?? foreground const accent = color('--accent') const ring = color('--ring') ?? foreground + const themed = input.painted['--editor-selection'] ? color('--editor-selection') : null + // Why the ring by default: a theme's accent is a faint hover tint, too weak to read as a selection. + const selection: Rgba = themed ?? [ring[0], ring[1], ring[2], 0.3] const popover = color('--popover') const border = color('--border') const colors: Record = { @@ -94,9 +97,13 @@ export function derivePluginMonacoTheme(input: { 'editorLineNumber.foreground': hex(muted), 'editorLineNumber.activeForeground': hex(foreground), 'editorCursor.foreground': hex(ring), - // Why from the ring: a theme's accent is a faint hover tint, too weak to read as a selection. - 'editor.selectionBackground': hex([ring[0], ring[1], ring[2], 0.3]), - 'editor.inactiveSelectionBackground': hex([ring[0], ring[1], ring[2], 0.15]) + 'editor.selectionBackground': hex(selection), + 'editor.inactiveSelectionBackground': hex([ + selection[0], + selection[1], + selection[2], + selection[3] / 2 + ]) } if (accent) { colors['editor.lineHighlightBackground'] = hex(accent) diff --git a/src/shared/plugins/artifacts/plugin-theme-artifact.ts b/src/shared/plugins/artifacts/plugin-theme-artifact.ts index 68f085ece3b..41d18c2efd9 100644 --- a/src/shared/plugins/artifacts/plugin-theme-artifact.ts +++ b/src/shared/plugins/artifacts/plugin-theme-artifact.ts @@ -68,6 +68,7 @@ export const PLUGIN_APP_THEME_TOKENS = [ '--chat-selection', '--chat-selection-foreground', '--text-link', + '--editor-selection', '--diff-added', '--diff-added-background', '--diff-added-emphasis', diff --git a/src/shared/plugins/artifacts/plugin-theme-css-fallbacks.ts b/src/shared/plugins/artifacts/plugin-theme-css-fallbacks.ts index 3c58e428705..27c1fdd2f73 100644 --- a/src/shared/plugins/artifacts/plugin-theme-css-fallbacks.ts +++ b/src/shared/plugins/artifacts/plugin-theme-css-fallbacks.ts @@ -74,7 +74,9 @@ const PLUGIN_APP_THEME_CSS_FALLBACKS: Record< light: { tint: '--text-link', percent: 30, into: '--chat-canvas' }, dark: { tint: '--text-link', percent: 40, into: '--chat-canvas' } }, - '--chat-selection-foreground': ['--foreground'] + '--chat-selection-foreground': ['--foreground'], + // Monaco paints this one, not main.css (plugin-monaco-theme.ts). + '--editor-selection': { tint: '--ring', percent: 30 } } export function pluginAppThemeCssFallback( diff --git a/src/shared/plugins/artifacts/plugin-theme-token-reference-doc.ts b/src/shared/plugins/artifacts/plugin-theme-token-reference-doc.ts index d0e380ce1cd..7f083343a84 100644 --- a/src/shared/plugins/artifacts/plugin-theme-token-reference-doc.ts +++ b/src/shared/plugins/artifacts/plugin-theme-token-reference-doc.ts @@ -73,6 +73,8 @@ const TOKEN_ROLES: Record = { '--chat-selection': 'Selected text in chat; may be translucent, it paints over the chat', '--chat-selection-foreground': 'Text inside a chat selection', '--text-link': 'Links in Markdown previews, editors and agent chat', + '--editor-selection': + 'Selected text in the code editor; keep it translucent so find matches show through. An unfocused editor shows it at half its opacity', '--diff-added': 'Added-line hue: Monaco overview ruler and chat diff text', '--diff-added-background': 'Added lines in diffs', '--diff-added-emphasis': 'Added characters within a changed line',