@@ -42,8 +42,7 @@ import { z } from 'zod';
4242
4343import type { Catalog , TableRef } from './sql-types' ;
4444
45- // Imperative handle exposed to the workspace so the catalog tree can open a
46- // query in a new editor tab (mirrors the prototype's editorRef).
45+ // Lets the workspace open a query in a new editor tab.
4746export type SqlEditorHandle = {
4847 /** Open `sql` in a new tab named `name` (or "Query N") and focus it. */
4948 setQuery : ( sql : string , name ?: string ) => void ;
@@ -98,10 +97,7 @@ type Tab = { id: number; name: string; sql: string };
9897const DEFAULT_QUERY =
9998 'SELECT vin, make, model, year, price_usd\nFROM default_redpanda_catalog=>cars\nWHERE in_stock = true\nORDER BY price_usd DESC\nLIMIT 100;' ;
10099
101- // Tracks the registry `.dark` class on the document root so the editor (whose
102- // highlight palette is built from theme-invariant color scales, not Tailwind
103- // classes) switches theme in lockstep with the rest of the surface. Uses
104- // useSyncExternalStore — no effect — per project style.
100+ // Re-render the editor when the registry `.dark` class toggles.
105101function subscribeToColorMode ( onStoreChange : ( ) => void ) : ( ) => void {
106102 const observer = new MutationObserver ( onStoreChange ) ;
107103 observer . observe ( document . documentElement , { attributes : true , attributeFilter : [ 'class' ] } ) ;
@@ -116,10 +112,7 @@ function useIsDarkMode(): boolean {
116112 return useSyncExternalStore ( subscribeToColorMode , getIsDarkSnapshot , ( ) => false ) ;
117113}
118114
119- // Editor chrome tuned to match the SQL Studio surface: transparent editor and
120- // gutter so the surrounding `bg-background` container shows through, with
121- // muted gutter line numbers. CodeMirror themes are plain CSS, so registry
122- // custom properties can be referenced directly and stay live.
115+ // Transparent editor/gutter so the `bg-background` surface shows through.
123116function editorChrome ( mode : 'light' | 'dark' ) : Extension {
124117 return EditorView . theme (
125118 {
@@ -130,6 +123,13 @@ function editorChrome(mode: 'light' | 'dark'): Extension {
130123 lineHeight : '21px' ,
131124 } ,
132125 '.cm-content' : { padding : '12px 0' } ,
126+ // A global `::selection` rule otherwise whitens selected text; theme the
127+ // selection pair instead. Full focused path matches the base rule's
128+ // specificity so this (later) theme wins.
129+ '& .cm-selectionBackground, &.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground' : {
130+ backgroundColor : 'var(--color-selection)' ,
131+ } ,
132+ '.cm-content ::selection' : { color : 'var(--color-selection-foreground)' } ,
133133 '.cm-gutters' : { backgroundColor : 'transparent' , border : 'none' , color : 'var(--color-muted-foreground)' } ,
134134 '.cm-activeLineGutter' : { backgroundColor : 'transparent' , color : 'var(--color-foreground)' } ,
135135 '.cm-activeLine' : { backgroundColor : 'var(--color-surface-default-hover)' } ,
@@ -138,9 +138,7 @@ function editorChrome(mode: 'light' | 'dark'): Extension {
138138 ) ;
139139}
140140
141- // SQL syntax palette mapped onto the Lezer highlight tags the SQL grammar
142- // emits, entirely from theme-adaptive semantic tokens so it tracks light/dark
143- // without per-mode values.
141+ // SQL syntax palette from semantic tokens, so it tracks light/dark.
144142function sqlHighlight ( ) : Extension {
145143 return syntaxHighlighting (
146144 HighlightStyle . define ( [
@@ -156,7 +154,7 @@ function sqlHighlight(): Extension {
156154 tag : [ tags . operator , tags . punctuation , tags . separator , tags . paren , tags . brace , tags . squareBracket ] ,
157155 color : 'var(--color-muted-foreground)' ,
158156 } ,
159- { tag : tags . name , color : 'var(--color-foreground )' } ,
157+ { tag : tags . name , color : 'var(--color-strong )' } ,
160158 ] )
161159 ) ;
162160}
@@ -264,12 +262,9 @@ function catalogNameCompletionResult(catalogs: Catalog[], from: number, before:
264262 } ;
265263}
266264
267- // Completion source for Redpanda SQL's catalog arrow notation. The generic
268- // schema completion can't model `catalog=>table`, so this source:
269- // - offers catalog names (boosted right after FROM/JOIN); applying one
270- // inserts `catalog=>` and immediately reopens completion for its tables
271- // - offers the catalog's tables after `catalog=>` — and after a typed
272- // `catalog.`, rewriting the dot to `=>` so users land on valid syntax
265+ // Completion for Oxla's `catalog=>table` notation (which the generic schema
266+ // completion can't model): catalog names after FROM/JOIN, then the catalog's
267+ // tables, rewriting a typed `catalog.` to `=>`.
273268function catalogArrowSource ( catalogs : Catalog [ ] ) : ( context : CompletionContext ) => CompletionResult | null {
274269 // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: CodeMirror completion context handling is branchy by API shape.
275270 return ( context ) => {
@@ -303,9 +298,8 @@ function catalogArrowSource(catalogs: Catalog[]): (context: CompletionContext) =
303298 } ;
304299}
305300
306- // Reformats the whole document through sql-formatter (dynamically imported to
307- // keep it out of the initial bundle; postgresql is the closest dialect to
308- // Oxla) as a single transaction, so undo restores the pre-format text.
301+ // Reformat via sql-formatter (lazy-loaded; postgresql ≈ Oxla) in one
302+ // transaction so undo restores the original.
309303async function formatDocument ( view : EditorView ) : Promise < void > {
310304 const { format } = await import ( 'sql-formatter' ) ;
311305 const current = view . state . doc . toString ( ) ;
@@ -391,9 +385,7 @@ export const SqlEditor = forwardRef<SqlEditorHandle, SqlEditorProps>(
391385 runText ( active . sql ) ;
392386 } ;
393387
394- // The Cmd/Ctrl+Enter keymap is part of the extensions array (rebuilt only on
395- // catalog/theme changes), so it reads fresh render state through this ref
396- // without an effect hook.
388+ // Keeps the keymap's run callback current without rebuilding the extensions.
397389 runRef . current = doRun ;
398390
399391 const runSelection = ( ) => {
@@ -405,8 +397,7 @@ export const SqlEditor = forwardRef<SqlEditorHandle, SqlEditorProps>(
405397 } ;
406398
407399 const extensions = useMemo ( ( ) => {
408- // No `schema` here — schemaColumnSource adds it back for dotted
409- // completions only, avoiding bare table names at the top level.
400+ // No `schema` here — schemaColumnSource adds it back for dotted members only.
410401 const sqlSupport = sqlLanguage ( { dialect : PostgreSQL , upperCaseKeywords : true } ) ;
411402 return [
412403 // Prec.highest so Mod-Enter beats the default keymap's insertBlankLine.
@@ -439,10 +430,8 @@ export const SqlEditor = forwardRef<SqlEditorHandle, SqlEditorProps>(
439430 if ( update . selectionSet ) {
440431 setHasSel ( ! update . state . selection . main . empty ) ;
441432 }
442- // Auto-open the catalog helper the moment the caller finishes typing
443- // `FROM `/`JOIN ` (a typed space), so `catalog=>` is suggested without
444- // a manual Ctrl+Space. Guarded to typing events to avoid re-triggering
445- // on programmatic edits (formatting, tab seeding).
433+ // Auto-open the catalog helper right after a typed `FROM `/`JOIN `
434+ // (typing events only, so formatting/seeding don't re-trigger it).
446435 if ( update . docChanged && update . transactions . some ( ( tr ) => tr . isUserEvent ( 'input.type' ) ) ) {
447436 const pos = update . state . selection . main . head ;
448437 const lineText = update . state . doc . lineAt ( pos ) ;
0 commit comments