diff --git a/apps/desktop/src/components/editor/QueryEditor.vue b/apps/desktop/src/components/editor/QueryEditor.vue index c2904966a..cf0c6e008 100644 --- a/apps/desktop/src/components/editor/QueryEditor.vue +++ b/apps/desktop/src/components/editor/QueryEditor.vue @@ -14,7 +14,7 @@ import { } from "@/lib/sqlCompletion"; import { extractIdentifierAt, isSqlKeyword, matchTable } from "@/lib/sqlNavigation"; import { lineColumnToOffset, parseSqlErrorLocation } from "@/lib/sqlDiagnostics"; -import { loadEditorTheme, editorFontTheme } from "@/lib/editorThemes"; +import { loadEditorTheme, editorFontTheme, sqlCompletionTheme } from "@/lib/editorThemes"; import { shortcutToCodeMirrorKey } from "@/lib/shortcutRegistry"; import type { SqlCompletionColumn } from "@/lib/sqlCompletion"; @@ -485,7 +485,7 @@ async function provideSqlCompletions( }) : { label: item.label, - type: item.type === "keyword" ? "keyword" : item.type === "table" ? "class" : "property", + type: item.type, detail: item.detail, boost: item.boost, }, @@ -595,6 +595,7 @@ onMounted(async () => { async (context: CompletionContext) => provideSqlCompletions(context.state, context.pos, context.explicit), ], }), + sqlCompletionTheme(EditorView), codeMirrorTheme.of(theme), closeBrackets(), bracketMatching(), diff --git a/apps/desktop/src/lib/editorThemes.ts b/apps/desktop/src/lib/editorThemes.ts index bf1d96c50..1bba0dc6e 100644 --- a/apps/desktop/src/lib/editorThemes.ts +++ b/apps/desktop/src/lib/editorThemes.ts @@ -1,6 +1,8 @@ import type { Extension } from "@codemirror/state"; import type { EditorTheme } from "@/stores/settingsStore"; +type CodeMirrorStyleSpec = Parameters[0]; + /** Load a CodeMirror theme extension by theme name. */ export async function loadEditorTheme(theme: EditorTheme): Promise { switch (theme) { @@ -41,5 +43,95 @@ export function editorFontTheme( }, ...(opts?.scrollable ? { ".cm-scroller": { overflow: "auto" } } : {}), ".cm-content": { fontFamily: family }, + ".cm-gutters": { + borderRight: "0 !important", + position: "relative", + }, + ".cm-gutters:after": { + background: "rgba(148, 163, 184, 0.38)", + bottom: "0", + content: "''", + pointerEvents: "none", + position: "absolute", + right: "0", + top: "0", + width: "1px", + zIndex: "10", + }, + ".cm-lineNumbers .cm-gutterElement": { + paddingRight: "16px", + }, }); } + +export function buildSqlCompletionThemeRules(): CodeMirrorStyleSpec { + return { + ".cm-tooltip.cm-tooltip-autocomplete": { + background: "#24272c", + border: "1px solid rgba(10, 12, 16, 0.95)", + borderRadius: "10px", + boxShadow: + "0 18px 42px rgba(0, 0, 0, 0.46), 0 0 0 1px rgba(255, 255, 255, 0.08) inset, 0 1px 0 rgba(255, 255, 255, 0.06) inset", + color: "rgba(202, 207, 217, 0.95)", + fontFamily: "var(--font-mono, 'JetBrains Mono', 'SF Mono', monospace)", + minWidth: "420px", + overflow: "hidden", + padding: "6px 0", + }, + ".cm-tooltip.cm-tooltip-autocomplete > ul": { + maxHeight: "340px", + minWidth: "420px", + padding: "0 7px 0 !important", + scrollbarColor: "rgba(148, 153, 162, 0.42) transparent", + scrollbarWidth: "thin", + }, + ".cm-tooltip.cm-tooltip-autocomplete > ul > li": { + alignItems: "center", + borderRadius: "6px", + color: "rgba(199, 204, 214, 0.92)", + display: "flex", + fontSize: "16px", + fontWeight: "760", + height: "34px", + letterSpacing: "0", + lineHeight: "34px", + padding: "0 18px !important", + transition: "background-color 90ms ease, color 90ms ease", + }, + ".cm-tooltip.cm-tooltip-autocomplete > ul > li[aria-selected]": { + background: "rgba(70, 75, 84, 0.86) !important", + color: "rgba(231, 235, 243, 0.98) !important", + }, + ".cm-completionIcon": { + display: "none !important", + height: "0", + margin: "0", + paddingRight: "0 !important", + width: "0", + }, + ".cm-completionLabel": { + color: "inherit", + fontFamily: "var(--font-mono, 'JetBrains Mono', 'SF Mono', monospace)", + fontSize: "16px", + fontWeight: "760", + letterSpacing: "0", + }, + ".cm-completionMatchedText": { + color: "#5794f9", + fontWeight: "860", + textDecoration: "none", + }, + ".cm-completionDetail": { + color: "rgba(184, 188, 198, 0.92)", + fontSize: "16px", + fontWeight: "760", + fontStyle: "normal", + marginLeft: "12px", + opacity: "1", + }, + }; +} + +export function sqlCompletionTheme(EditorView: typeof import("@codemirror/view").EditorView): Extension { + return EditorView.theme(buildSqlCompletionThemeRules()); +} diff --git a/packages/app-tests/editorCompletionTheme.test.ts b/packages/app-tests/editorCompletionTheme.test.ts new file mode 100644 index 000000000..e5ba2daa3 --- /dev/null +++ b/packages/app-tests/editorCompletionTheme.test.ts @@ -0,0 +1,28 @@ +import { strict as assert } from "node:assert"; +import test from "node:test"; +import { buildSqlCompletionThemeRules } from "../../apps/desktop/src/lib/editorThemes.ts"; + +test("sql completion theme styles the autocomplete popup", () => { + const rules = buildSqlCompletionThemeRules(); + + assert.ok(rules[".cm-tooltip.cm-tooltip-autocomplete"]); + assert.deepEqual(rules[".cm-completionIcon"], { + display: "none !important", + height: "0", + margin: "0", + paddingRight: "0 !important", + width: "0", + }); + assert.deepEqual(rules[".cm-completionLabel"], { + color: "inherit", + fontFamily: "var(--font-mono, 'JetBrains Mono', 'SF Mono', monospace)", + fontSize: "16px", + fontWeight: "760", + letterSpacing: "0", + }); + assert.equal(rules[".cm-completionMatchedText"]?.color, "#5794f9"); + assert.equal( + rules[".cm-tooltip.cm-tooltip-autocomplete > ul > li[aria-selected]"]?.background, + "rgba(70, 75, 84, 0.86) !important", + ); +});