diff --git a/apps/desktop/src/components/editor/ThemeCustomizerDialog.vue b/apps/desktop/src/components/editor/ThemeCustomizerDialog.vue index 6fd2eb603e..246aa45b1b 100644 --- a/apps/desktop/src/components/editor/ThemeCustomizerDialog.vue +++ b/apps/desktop/src/components/editor/ThemeCustomizerDialog.vue @@ -5,7 +5,7 @@ import { Button } from "@/components/ui/button"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Input } from "@/components/ui/input"; import type { CustomTheme, CustomThemeColors } from "@/stores/settingsStore"; -import { DEFAULT_CUSTOM_THEMES, DEFAULT_CUSTOM_THEME_COLORS, DEFAULT_CUSTOM_THEME_DDL_COLORS } from "@/stores/settingsStore"; +import { DEFAULT_CUSTOM_THEMES, DEFAULT_CUSTOM_THEME_COLORS, DEFAULT_CUSTOM_THEME_DDL_COLORS, DEFAULT_CUSTOM_THEME_OPTIONAL_COLORS } from "@/stores/settingsStore"; import { Plus, Trash2, Copy, Pencil, ChevronDown, Palette } from "@lucide/vue"; import { useToast } from "@/composables/useToast"; import { useI18n } from "vue-i18n"; @@ -70,6 +70,10 @@ const localColors = computed({ }, }); +function getDisplayColor(key: keyof CustomThemeColors): string { + return localColors.value[key] ?? DEFAULT_CUSTOM_THEME_OPTIONAL_COLORS[key] ?? "#000000"; +} + watch( localThemes, () => { @@ -91,6 +95,12 @@ const colorItems = [ { key: "builtin" as const, label: t("settings.customThemeBuiltin"), example: "FOUND, SQLERRM", num: "⑩" }, { key: "background" as const, label: t("settings.customThemeBackground"), example: "Editor background", num: "⑪" }, { key: "foreground" as const, label: t("settings.customThemeForeground"), example: "Default text color", num: "⑫" }, + { key: "activeLine" as const, label: t("settings.customThemeActiveLine"), example: "Current line highlight", num: "⑬" }, + { key: "selection" as const, label: t("settings.customThemeSelection"), example: "Selection background", num: "⑭" }, + { key: "cursor" as const, label: t("settings.customThemeCursor"), example: "Cursor line/caret", num: "⑮" }, + { key: "gutterBackground" as const, label: t("settings.customThemeGutterBackground"), example: "Gutter background", num: "⑯" }, + { key: "lineNumber" as const, label: t("settings.customThemeLineNumber"), example: "Line numbers", num: "⑰" }, + { key: "matchingBracket" as const, label: t("settings.customThemeMatchingBracket"), example: "Matching bracket highlight", num: "⑱" }, ]; // Preset color themes (including all built-in themes) @@ -537,7 +547,7 @@ function handleImport() {
@@ -548,8 +558,8 @@ function handleImport() {
- - + +
diff --git a/apps/desktop/src/i18n/locales/az.ts b/apps/desktop/src/i18n/locales/az.ts index e4b6e9227a..be10fe04db 100644 --- a/apps/desktop/src/i18n/locales/az.ts +++ b/apps/desktop/src/i18n/locales/az.ts @@ -7806,6 +7806,12 @@ export default withEnglishFallback({ customThemeBuiltin: "Daxili", customThemeBackground: "Arxa plan", customThemeForeground: "Ön plan", + customThemeActiveLine: "Aktiv sətir", + customThemeSelection: "Seçim", + customThemeCursor: "Kursor", + customThemeGutterBackground: "Sətir kənarı fonu", + customThemeLineNumber: "Sətir nömrəsi", + customThemeMatchingBracket: "Uyğun mötərizə", customThemeDefaultName: "Fərdi tema", customThemeCopySuffix: " surəti", customThemeApplied: '"{name}" rəng sxemi tətbiq edildi', diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts index 81c31b903e..afe148d379 100644 --- a/apps/desktop/src/i18n/locales/en.ts +++ b/apps/desktop/src/i18n/locales/en.ts @@ -8736,6 +8736,12 @@ export default { customThemeBuiltin: "Builtin", customThemeBackground: "Background", customThemeForeground: "Foreground", + customThemeActiveLine: "Active Line", + customThemeSelection: "Selection", + customThemeCursor: "Cursor", + customThemeGutterBackground: "Gutter Background", + customThemeLineNumber: "Line Number", + customThemeMatchingBracket: "Matching Bracket", customThemeDefaultName: "Custom Theme", customThemeCopySuffix: " Copy", customThemeApplied: 'Applied "{name}" color scheme', diff --git a/apps/desktop/src/i18n/locales/es.ts b/apps/desktop/src/i18n/locales/es.ts index 3206da91eb..046a8b40ce 100644 --- a/apps/desktop/src/i18n/locales/es.ts +++ b/apps/desktop/src/i18n/locales/es.ts @@ -8036,6 +8036,12 @@ export default withEnglishFallback({ customThemeBuiltin: "Variable integrada", customThemeBackground: "Color de fondo", customThemeForeground: "Color de primer plano", + customThemeActiveLine: "Línea activa", + customThemeSelection: "Selección", + customThemeCursor: "Cursor", + customThemeGutterBackground: "Fondo del margen de líneas", + customThemeLineNumber: "Número de línea", + customThemeMatchingBracket: "Paréntesis coincidente", customThemeDefaultName: "Tema personalizado", customThemeCopySuffix: " Copia", customThemeApplied: 'Esquema de color "{name}" aplicado', diff --git a/apps/desktop/src/i18n/locales/id.ts b/apps/desktop/src/i18n/locales/id.ts index 754772e789..83a2e38a64 100644 --- a/apps/desktop/src/i18n/locales/id.ts +++ b/apps/desktop/src/i18n/locales/id.ts @@ -8529,6 +8529,12 @@ export default withEnglishFallback({ customThemeBuiltin: "Bawaan", customThemeBackground: "Latar Belakang", customThemeForeground: "Latar Depan", + customThemeActiveLine: "Baris Aktif", + customThemeSelection: "Pilihan", + customThemeCursor: "Kursor", + customThemeGutterBackground: "Latar Belakang Margin", + customThemeLineNumber: "Nomor Baris", + customThemeMatchingBracket: "Tanda Kurung Cocok", customThemeDefaultName: "Tema Kustom", customThemeCopySuffix: " Salinan", customThemeApplied: 'Skema warna "{name}" diterapkan', diff --git a/apps/desktop/src/i18n/locales/it.ts b/apps/desktop/src/i18n/locales/it.ts index f58672a1f1..97ae2a6ad7 100644 --- a/apps/desktop/src/i18n/locales/it.ts +++ b/apps/desktop/src/i18n/locales/it.ts @@ -7798,6 +7798,12 @@ export default withEnglishFallback({ customThemeBuiltin: "Integrato", customThemeBackground: "Sfondo", customThemeForeground: "Primo piano", + customThemeActiveLine: "Riga attiva", + customThemeSelection: "Selezione", + customThemeCursor: "Cursore", + customThemeGutterBackground: "Sfondo del margine", + customThemeLineNumber: "Numero di riga", + customThemeMatchingBracket: "Parentesi corrispondente", customThemeDefaultName: "Tema personalizzato", customThemeCopySuffix: " Copia", customThemeApplied: 'Schema colori "{name}" applicato', diff --git a/apps/desktop/src/i18n/locales/ja.ts b/apps/desktop/src/i18n/locales/ja.ts index a833a7003c..f198a44adf 100644 --- a/apps/desktop/src/i18n/locales/ja.ts +++ b/apps/desktop/src/i18n/locales/ja.ts @@ -7793,6 +7793,12 @@ export default withEnglishFallback({ customThemeBuiltin: "組み込み", customThemeBackground: "背景", customThemeForeground: "前景", + customThemeActiveLine: "アクティブ行", + customThemeSelection: "選択範囲", + customThemeCursor: "カーソル", + customThemeGutterBackground: "ガター背景", + customThemeLineNumber: "行番号", + customThemeMatchingBracket: "対応する括弧", customThemeDefaultName: "カスタムテーマ", customThemeCopySuffix: "コピー", customThemeApplied: "配色テーマ「{name}」を適用しました", diff --git a/apps/desktop/src/i18n/locales/ko.ts b/apps/desktop/src/i18n/locales/ko.ts index 94c55fe763..cff83151ee 100644 --- a/apps/desktop/src/i18n/locales/ko.ts +++ b/apps/desktop/src/i18n/locales/ko.ts @@ -7606,6 +7606,12 @@ export default withEnglishFallback({ customThemeBuiltin: "내장", customThemeBackground: "배경", customThemeForeground: "전경", + customThemeActiveLine: "현재 줄", + customThemeSelection: "선택 영역", + customThemeCursor: "커서", + customThemeGutterBackground: "여백 배경", + customThemeLineNumber: "줄 번호", + customThemeMatchingBracket: "일치하는 괄호", customThemeDefaultName: "사용자 지정 테마", customThemeCopySuffix: " 사본", customThemeApplied: '"{name}" 색 구성표를 적용했습니다', diff --git a/apps/desktop/src/i18n/locales/pt-BR.ts b/apps/desktop/src/i18n/locales/pt-BR.ts index 6498877bd2..8aae7086d9 100644 --- a/apps/desktop/src/i18n/locales/pt-BR.ts +++ b/apps/desktop/src/i18n/locales/pt-BR.ts @@ -7799,6 +7799,12 @@ export default withEnglishFallback({ customThemeBuiltin: "Integrado", customThemeBackground: "Plano de fundo", customThemeForeground: "Primeiro plano", + customThemeActiveLine: "Linha ativa", + customThemeSelection: "Seleção", + customThemeCursor: "Cursor", + customThemeGutterBackground: "Fundo da margem", + customThemeLineNumber: "Número da linha", + customThemeMatchingBracket: "Parêntese correspondente", customThemeDefaultName: "Tema personalizado", customThemeCopySuffix: " Cópia", customThemeApplied: 'Esquema de cores "{name}" aplicado', diff --git a/apps/desktop/src/i18n/locales/ru.ts b/apps/desktop/src/i18n/locales/ru.ts index cca76f10fd..4e2c064ba7 100644 --- a/apps/desktop/src/i18n/locales/ru.ts +++ b/apps/desktop/src/i18n/locales/ru.ts @@ -9050,6 +9050,12 @@ export default withEnglishFallback({ customThemeBuiltin: "Встроенный", customThemeBackground: "Фон", customThemeForeground: "Передний план", + customThemeActiveLine: "Текущая строка", + customThemeSelection: "Выделение", + customThemeCursor: "Курсор", + customThemeGutterBackground: "Фон полосы номеров строк", + customThemeLineNumber: "Номер строки", + customThemeMatchingBracket: "Парная скобка", customThemeDefaultName: "Своя тема", customThemeCopySuffix: " (копия)", customThemeApplied: "Применена цветовая схема «{name}»", diff --git a/apps/desktop/src/i18n/locales/tr.ts b/apps/desktop/src/i18n/locales/tr.ts index b6bb91ef2d..44e7fc139c 100644 --- a/apps/desktop/src/i18n/locales/tr.ts +++ b/apps/desktop/src/i18n/locales/tr.ts @@ -7707,6 +7707,12 @@ export default withEnglishFallback({ customThemeBuiltin: "Yerleşik", customThemeBackground: "Arka plan", customThemeForeground: "Ön plan", + customThemeActiveLine: "Etkin satır", + customThemeSelection: "Seçim", + customThemeCursor: "İmleç", + customThemeGutterBackground: "Kenar boşluğu arka planı", + customThemeLineNumber: "Satır numarası", + customThemeMatchingBracket: "Eşleşen parantez", customThemeDefaultName: "Özel Tema", customThemeCopySuffix: " Kopya", customThemeApplied: '"{name}" renk şeması uygulandı', diff --git a/apps/desktop/src/i18n/locales/zh-CN.ts b/apps/desktop/src/i18n/locales/zh-CN.ts index 70515cd2ba..6a0d135434 100644 --- a/apps/desktop/src/i18n/locales/zh-CN.ts +++ b/apps/desktop/src/i18n/locales/zh-CN.ts @@ -8722,6 +8722,12 @@ export default withEnglishFallback({ customThemeBuiltin: "内置变量", customThemeBackground: "背景色", customThemeForeground: "前景色", + customThemeActiveLine: "当前行高亮", + customThemeSelection: "选中文本", + customThemeCursor: "光标颜色", + customThemeGutterBackground: "行号栏背景", + customThemeLineNumber: "行号颜色", + customThemeMatchingBracket: "匹配括号", customThemeDefaultName: "自定义主题", customThemeCopySuffix: " 副本", customThemeApplied: "已应用「{name}」配色方案", diff --git a/apps/desktop/src/i18n/locales/zh-TW.ts b/apps/desktop/src/i18n/locales/zh-TW.ts index 66966c2184..f9a0b60daf 100644 --- a/apps/desktop/src/i18n/locales/zh-TW.ts +++ b/apps/desktop/src/i18n/locales/zh-TW.ts @@ -7124,6 +7124,12 @@ export default withEnglishFallback({ customThemeBuiltin: "內置變量", customThemeBackground: "背景色", customThemeForeground: "前景色", + customThemeActiveLine: "當前行高亮", + customThemeSelection: "選中文本", + customThemeCursor: "光標顏色", + customThemeGutterBackground: "行號欄背景", + customThemeLineNumber: "行號顏色", + customThemeMatchingBracket: "匹配括號", customThemeDefaultName: "自定義主題", customThemeCopySuffix: " 副本", customThemeApplied: "已應用「{name}」配色方案", diff --git a/apps/desktop/src/lib/__tests__/editorThemes.spec.ts b/apps/desktop/src/lib/__tests__/editorThemes.spec.ts index 3366963833..c826862302 100644 --- a/apps/desktop/src/lib/__tests__/editorThemes.spec.ts +++ b/apps/desktop/src/lib/__tests__/editorThemes.spec.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from "vitest"; -import { buildEditorFontThemeRules, buildSqlCompletionThemeRules, editorDiagnosticColors, editorThemeAppearanceFor, IDE_EDITOR_THEMES, resolveCustomThemeBackgrounds, resolveEditorTheme, SQL_BUILTIN_HIGHLIGHT_TAG } from "@/lib/editor/editorThemes"; +import { buildEditorFontThemeRules, buildSqlCompletionThemeRules, editorDiagnosticColors, editorThemeAppearanceFor, IDE_EDITOR_THEMES, loadEditorTheme, resolveCustomThemeBackgrounds, resolveEditorTheme, SQL_BUILTIN_HIGHLIGHT_TAG } from "@/lib/editor/editorThemes"; import { DEFAULT_APP_CUSTOM_UI_COLORS, wcagContrastRatio, type AppThemePalette } from "@/lib/app/appTheme"; -import type { EditorTheme } from "@/stores/settingsStore"; +import { DEFAULT_CUSTOM_THEME_COLORS, type CustomThemeColors, type EditorTheme } from "@/stores/settingsStore"; import { createDbxCodeMirrorSqlDialect } from "@/lib/editor/codemirrorSqlDialect"; import * as langSql from "@codemirror/lang-sql"; @@ -61,6 +61,31 @@ describe("custom editor theme backgrounds", () => { }); }); + it("uses an explicit gutter background when provided", () => { + expect(resolveCustomThemeBackgrounds({ background: "#10131a", gutterBackground: "#0b0c10" }, true)).toEqual({ + background: "#10131a", + gutterBackground: "#0b0c10", + }); + expect(resolveCustomThemeBackgrounds({ gutterBackground: "#0b0c10" }, false)).toEqual({ + background: "#fafafa", + gutterBackground: "#0b0c10", + }); + }); + + it("loads custom editor theme with custom UI colors", async () => { + const customColors: CustomThemeColors = { + ...DEFAULT_CUSTOM_THEME_COLORS, + activeLine: "#2a2a3a", + selection: "#3a3a4a", + cursor: "#ff00ff", + gutterBackground: "#111122", + lineNumber: "#777788", + matchingBracket: "#444455", + }; + const extension = await loadEditorTheme("custom", "dark", customColors); + expect(extension).toBeDefined(); + }); + it("keeps the existing custom defaults when background is omitted", () => { expect(resolveCustomThemeBackgrounds(undefined, true)).toEqual({ background: "#1e1e2e", diff --git a/apps/desktop/src/lib/editor/editorThemes.ts b/apps/desktop/src/lib/editor/editorThemes.ts index cb8693af39..3fe24ff518 100644 --- a/apps/desktop/src/lib/editor/editorThemes.ts +++ b/apps/desktop/src/lib/editor/editorThemes.ts @@ -92,10 +92,10 @@ const customThemeColors = { invalid: "#f38ba8", // 无效字符 }; -export function resolveCustomThemeBackgrounds(colors?: Pick, isDark: boolean = true): { background: string; gutterBackground: string } { +export function resolveCustomThemeBackgrounds(colors?: Pick, isDark: boolean = true): { background: string; gutterBackground: string } { return { background: colors?.background ?? (isDark ? "#1e1e2e" : "#fafafa"), - gutterBackground: colors?.background ?? customThemeColors.gutterBackground, + gutterBackground: colors?.gutterBackground ?? colors?.background ?? customThemeColors.gutterBackground, }; } diff --git a/apps/desktop/src/lib/settings/__tests__/settingsTransfer.spec.ts b/apps/desktop/src/lib/settings/__tests__/settingsTransfer.spec.ts index fce21c24fb..41fa6f28ac 100644 --- a/apps/desktop/src/lib/settings/__tests__/settingsTransfer.spec.ts +++ b/apps/desktop/src/lib/settings/__tests__/settingsTransfer.spec.ts @@ -350,6 +350,42 @@ describe("settingsTransfer", () => { expect(result.value.editorSettings.customThemes).toEqual([validTheme]); }); + it("accepts custom theme items with optional UI colors", () => { + const base = DEFAULT_EDITOR_SETTINGS.customThemes[0]; + const validTheme = { + ...base, + id: "t1", + name: "T1", + colors: { + ...base.colors, + background: "#282c34", + foreground: "#abb2bf", + activeLine: "#2c313a", + selection: "#3e4451", + cursor: "#528bff", + gutterBackground: "#21252b", + lineNumber: "#4b5263", + matchingBracket: "#515a6b", + }, + }; + const result = parseSettingsTransferFile(fileWith({ customThemes: [validTheme] })); + expect(result.ok).toBe(true); + if (!result.ok) return; + expect(result.value.editorSettings.customThemes).toEqual([validTheme]); + }); + + it("rejects custom theme items with wrong-typed optional UI colors", () => { + const base = DEFAULT_EDITOR_SETTINGS.customThemes[0]; + const result = parseSettingsTransferFile( + fileWith({ + customThemes: [{ ...base, id: "t1", name: "T1", colors: { ...base.colors, activeLine: 123 } }], + }), + ); + expect(result.ok).toBe(false); + if (result.ok) return; + expect(result.error.detail).toContain("customThemes"); + }); + it("rejects null custom theme items instead of crashing", () => { const result = parseSettingsTransferFile(fileWith({ customThemes: [null] })); expect(result.ok).toBe(false); diff --git a/apps/desktop/src/lib/settings/settingsTransfer.ts b/apps/desktop/src/lib/settings/settingsTransfer.ts index decacf6ac3..a958087607 100644 --- a/apps/desktop/src/lib/settings/settingsTransfer.ts +++ b/apps/desktop/src/lib/settings/settingsTransfer.ts @@ -349,7 +349,7 @@ const CUSTOM_THEME_DDL_COLORS_SHAPE = valueKindShape(DEFAULT_CUSTOM_THEME_DDL_CO * cover them and a wrong-typed value (e.g. `background: {}`) would otherwise * pass validation and reach the persisted settings. */ -const CUSTOM_THEME_OPTIONAL_COLOR_KEYS = ["background", "foreground"] as const; +const CUSTOM_THEME_OPTIONAL_COLOR_KEYS = ["background", "foreground", "activeLine", "selection", "cursor", "gutterBackground", "lineNumber", "matchingBracket"] as const; function hasValidOptionalColorKinds(value: unknown): boolean { if (!isPlainObject(value)) return false; diff --git a/apps/desktop/src/stores/__tests__/settingsStore.spec.ts b/apps/desktop/src/stores/__tests__/settingsStore.spec.ts index 17aca1adc2..19b0d8eeeb 100644 --- a/apps/desktop/src/stores/__tests__/settingsStore.spec.ts +++ b/apps/desktop/src/stores/__tests__/settingsStore.spec.ts @@ -5,7 +5,9 @@ import { AI_PROVIDER_PARTNER_PRESETS, AI_PROVIDER_PRESETS, DEFAULT_CUSTOM_THEMES, + DEFAULT_CUSTOM_THEME_COLORS, DEFAULT_EDITOR_SETTINGS, + type CustomThemeColors, EXECUTE_MODE_CURRENT_DEFAULT_VERSION, SIDEBAR_BROWSE_OBJECTS_MIGRATION_VERSION, enforceRightSidebarPanelExclusivity, @@ -131,6 +133,39 @@ describe("normalizeEditorSettings", () => { }); }); + it("preserves optional UI colors in customThemes and customThemeColors", () => { + const customColors: CustomThemeColors = { + ...DEFAULT_CUSTOM_THEME_COLORS, + background: "#101010", + foreground: "#f0f0f0", + activeLine: "#202020", + selection: "#303030", + cursor: "#ff00ff", + gutterBackground: "#151515", + lineNumber: "#666666", + matchingBracket: "#444444", + }; + const normalized = normalizeEditorSettings({ + customThemeColors: customColors, + customThemes: [ + { + id: "custom-1", + name: "My Custom", + colors: customColors, + ddlColors: DEFAULT_CUSTOM_THEMES[0].ddlColors, + }, + ], + }); + expect(normalized.customThemeColors.activeLine).toBe("#202020"); + expect(normalized.customThemeColors.selection).toBe("#303030"); + expect(normalized.customThemeColors.cursor).toBe("#ff00ff"); + expect(normalized.customThemeColors.gutterBackground).toBe("#151515"); + expect(normalized.customThemeColors.lineNumber).toBe("#666666"); + expect(normalized.customThemeColors.matchingBracket).toBe("#444444"); + expect(normalized.customThemes[0].colors.activeLine).toBe("#202020"); + expect(normalized.customThemes[0].colors.gutterBackground).toBe("#151515"); + }); + it("normalizes customThemes when empty or absent by seeding DEFAULT_CUSTOM_THEMES", () => { // When customThemes is an empty array const normalizedEmpty = normalizeEditorSettings({ customThemes: [] }); diff --git a/apps/desktop/src/stores/settingsStore.ts b/apps/desktop/src/stores/settingsStore.ts index a4d7e31c33..fcc52059b5 100644 --- a/apps/desktop/src/stores/settingsStore.ts +++ b/apps/desktop/src/stores/settingsStore.ts @@ -769,8 +769,25 @@ export interface CustomThemeColors { builtin: string; background?: string; foreground?: string; + activeLine?: string; + selection?: string; + cursor?: string; + gutterBackground?: string; + lineNumber?: string; + matchingBracket?: string; } +export const DEFAULT_CUSTOM_THEME_OPTIONAL_COLORS: Partial> = { + background: "#1e1e2e", + foreground: "#cdd6f4", + activeLine: "#313244", + selection: "#313244", + cursor: "#f5e0dc", + gutterBackground: "#181825", + lineNumber: "#6c7086", + matchingBracket: "#45475a", +}; + export const DEFAULT_CUSTOM_THEME_COLORS: CustomThemeColors = { keyword: "#cba6f7", field: "#f9e2af",