@@ -15,7 +15,7 @@ import {
1515 type CompletionResult ,
1616 startCompletion ,
1717} from '@codemirror/autocomplete' ;
18- import { PostgreSQL , type SQLNamespace , sql as sqlLanguage } from '@codemirror/lang-sql' ;
18+ import { PostgreSQL , type SQLNamespace , schemaCompletionSource , sql as sqlLanguage } from '@codemirror/lang-sql' ;
1919import { HighlightStyle , indentUnit , syntaxHighlighting , syntaxTree } from '@codemirror/language' ;
2020import { EditorState , type Extension , Prec } from '@codemirror/state' ;
2121import { EditorView , keymap } from '@codemirror/view' ;
@@ -121,8 +121,6 @@ function useIsDarkMode(): boolean {
121121// muted gutter line numbers. CodeMirror themes are plain CSS, so registry
122122// custom properties can be referenced directly and stay live.
123123function editorChrome ( mode : 'light' | 'dark' ) : Extension {
124- const gutter = mode === 'dark' ? 'var(--color-grey-600)' : 'var(--color-grey-400)' ;
125- const gutterActive = mode === 'dark' ? 'var(--color-grey-400)' : 'var(--color-grey-600)' ;
126124 return EditorView . theme (
127125 {
128126 '&' : { backgroundColor : 'transparent' , height : '100%' , fontSize : '13px' } ,
@@ -132,58 +130,39 @@ function editorChrome(mode: 'light' | 'dark'): Extension {
132130 lineHeight : '21px' ,
133131 } ,
134132 '.cm-content' : { padding : '12px 0' } ,
135- '.cm-gutters' : { backgroundColor : 'transparent' , border : 'none' , color : gutter } ,
136- '.cm-activeLineGutter' : { backgroundColor : 'transparent' , color : gutterActive } ,
137- '.cm-activeLine' : {
138- backgroundColor : mode === 'dark' ? 'rgba(255, 255, 255, 0.04)' : 'rgba(0, 0, 0, 0.03)' ,
139- } ,
133+ '.cm-gutters' : { backgroundColor : 'transparent' , border : 'none' , color : 'var(--color-muted-foreground)' } ,
134+ '.cm-activeLineGutter' : { backgroundColor : 'transparent' , color : 'var(--color-foreground)' } ,
135+ '.cm-activeLine' : { backgroundColor : 'var(--color-surface-default-hover)' } ,
140136 } ,
141137 { dark : mode === 'dark' }
142138 ) ;
143139}
144140
145- // SQL syntax palette, mapped from the design's `.sql-*` token classes onto the
146- // Lezer highlight tags the SQL grammar emits (keywords, built-ins, strings,
147- // numbers, comments, operators/punctuation and identifiers).
148- function sqlHighlight ( mode : 'light' | 'dark' ) : Extension {
149- const c =
150- mode === 'dark'
151- ? {
152- keyword : 'var(--color-purple-300)' ,
153- fn : 'var(--color-indigo-300)' ,
154- str : 'var(--color-green-300)' ,
155- num : 'var(--color-orange-300)' ,
156- comment : 'var(--color-grey-400)' ,
157- punct : 'var(--color-grey-500)' ,
158- id : 'var(--color-grey-100)' ,
159- }
160- : {
161- keyword : 'var(--color-purple-700)' ,
162- fn : 'var(--color-indigo-600)' ,
163- str : 'var(--color-green-700)' ,
164- num : 'var(--color-orange-700)' ,
165- comment : 'var(--color-grey-600)' ,
166- punct : 'var(--color-grey-500)' ,
167- id : 'var(--color-grey-900)' ,
168- } ;
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.
144+ function sqlHighlight ( ) : Extension {
169145 return syntaxHighlighting (
170146 HighlightStyle . define ( [
171- { tag : tags . keyword , color : c . keyword , fontWeight : 'bold' } ,
172- { tag : [ tags . standard ( tags . name ) , tags . function ( tags . variableName ) , tags . typeName ] , color : c . fn } ,
173- { tag : [ tags . string , tags . special ( tags . string ) ] , color : c . str } ,
174- { tag : tags . number , color : c . num } ,
175- { tag : tags . comment , color : c . comment , fontStyle : 'italic' } ,
147+ { tag : tags . keyword , color : 'var(--color-secondary)' , fontWeight : 'bold' } ,
148+ {
149+ tag : [ tags . standard ( tags . name ) , tags . function ( tags . variableName ) , tags . typeName ] ,
150+ color : 'var(--color-primary)' ,
151+ } ,
152+ { tag : [ tags . string , tags . special ( tags . string ) ] , color : 'var(--color-success)' } ,
153+ { tag : tags . number , color : 'var(--color-warning)' } ,
154+ { tag : tags . comment , color : 'var(--color-muted-foreground)' , fontStyle : 'italic' } ,
176155 {
177156 tag : [ tags . operator , tags . punctuation , tags . separator , tags . paren , tags . brace , tags . squareBracket ] ,
178- color : c . punct ,
157+ color : 'var(--color-muted-foreground)' ,
179158 } ,
180- { tag : tags . name , color : c . id } ,
159+ { tag : tags . name , color : 'var(--color-foreground)' } ,
181160 ] )
182161 ) ;
183162}
184163
185- const LIGHT_THEME : Extension = [ editorChrome ( 'light' ) , sqlHighlight ( 'light' ) ] ;
186- const DARK_THEME : Extension = [ editorChrome ( 'dark' ) , sqlHighlight ( 'dark' ) ] ;
164+ const LIGHT_THEME : Extension = [ editorChrome ( 'light' ) , sqlHighlight ( ) ] ;
165+ const DARK_THEME : Extension = [ editorChrome ( 'dark' ) , sqlHighlight ( ) ] ;
187166
188167function tableNamespace ( table : TableRef ) : SQLNamespace {
189168 return {
@@ -192,12 +171,9 @@ function tableNamespace(table: TableRef): SQLNamespace {
192171 } ;
193172}
194173
195- // Builds the lang-sql completion schema from the loaded catalog tree: bare
196- // table names → columns. Tables are deliberately NOT nested under their
197- // catalog — Redpanda SQL (Oxla) addresses catalog tables with arrow notation
198- // (`catalog=>table`), which catalogArrowSource below handles; dot-style
199- // nesting would advertise syntax the server rejects. Bare entries still give
200- // alias/column resolution (`FROM default_redpanda_catalog=>cars c` → `c.`).
174+ // Bare table name → columns. Powers alias/column resolution only
175+ // (schemaColumnSource); tables aren't nested under catalogs since Oxla uses
176+ // `catalog=>table` arrow notation, handled by catalogArrowSource.
201177function buildSchema ( catalogs : Catalog [ ] ) : SQLNamespace {
202178 const root : Record < string , SQLNamespace > = { } ;
203179 for ( const catalog of catalogs ) {
@@ -212,6 +188,20 @@ function buildSchema(catalogs: Catalog[]): SQLNamespace {
212188 return root ;
213189}
214190
191+ // Schema completion limited to dotted members (`c.` → columns); bare table
192+ // names are suppressed at the top level so catalogArrowSource owns them.
193+ function schemaColumnSource ( catalogs : Catalog [ ] ) : ( context : CompletionContext ) => CompletionResult | null {
194+ const source = schemaCompletionSource ( { dialect : PostgreSQL , schema : buildSchema ( catalogs ) } ) ;
195+ return ( context ) => {
196+ const result = source ( context ) ;
197+ if ( ! result || result instanceof Promise ) {
198+ return null ;
199+ }
200+ const dotted = context . state . sliceDoc ( result . from - 1 , result . from ) === '.' ;
201+ return dotted ? result : null ;
202+ } ;
203+ }
204+
215205// Matches an identifier followed by `=>` or `.` and a partial table name,
216206// anchored at the cursor: [, name, gap1, separator, gap2, quote, partial].
217207const CATALOG_REF_RE = / ( [ A - Z a - z _ ] [ \w $ ] * ) ( \s * ) ( = > | \. ) ( \s * ) ( " ? ) ( [ \w $ ] * ) $ / ;
@@ -415,7 +405,9 @@ export const SqlEditor = forwardRef<SqlEditorHandle, SqlEditorProps>(
415405 } ;
416406
417407 const extensions = useMemo ( ( ) => {
418- const sqlSupport = sqlLanguage ( { dialect : PostgreSQL , schema : buildSchema ( catalogs ) , upperCaseKeywords : true } ) ;
408+ // No `schema` here — schemaColumnSource adds it back for dotted
409+ // completions only, avoiding bare table names at the top level.
410+ const sqlSupport = sqlLanguage ( { dialect : PostgreSQL , upperCaseKeywords : true } ) ;
419411 return [
420412 // Prec.highest so Mod-Enter beats the default keymap's insertBlankLine.
421413 Prec . highest (
@@ -441,6 +433,7 @@ export const SqlEditor = forwardRef<SqlEditorHandle, SqlEditorProps>(
441433 ) ,
442434 sqlSupport ,
443435 sqlSupport . language . data . of ( { autocomplete : catalogArrowSource ( catalogs ) } ) ,
436+ sqlSupport . language . data . of ( { autocomplete : schemaColumnSource ( catalogs ) } ) ,
444437 isDark ? DARK_THEME : LIGHT_THEME ,
445438 EditorView . updateListener . of ( ( update ) => {
446439 if ( update . selectionSet ) {
0 commit comments