1- import "@/editorWorker" ;
2- import * as monaco from "monaco-editor/esm/vs/editor/editor.api" ;
1+ import { FunctionComponent , useEffect , useRef } from "react" ;
2+
3+ import type { IDisposable } from "monaco-editor" ;
34import { vsPlusTheme } from "monaco-sql-languages" ;
4- import { FunctionComponent , useEffect , useRef , useState } from "react" ;
5+ import EditorComponent , { useMonaco } from "@monaco-editor/ react" ;
56
6- import { useTheme } from "@/provider/theme.provider" ;
77import {
88 COMMAND_CONFIG ,
99 ID_LANGUAGE_SQL ,
1010 autoSuggestionCompletionItems ,
1111} from "./editor.config" ;
12- import { Card } from "./ui/card" ;
12+
1313import { fetchAutocomplete } from "@/api" ;
14+ import { Card } from "@/components/ui/card" ;
1415import { useQuery } from "@tanstack/react-query" ;
16+ import { useTheme } from "@/provider/theme.provider" ;
1517
1618type Props = {
1719 value : string ;
@@ -20,151 +22,125 @@ type Props = {
2022
2123export const Editor : FunctionComponent < Props > = ( { value, onChange } ) => {
2224 const currentTheme = useTheme ( ) ;
23- const [ editor , setEditor ] =
24- useState < monaco . editor . IStandaloneCodeEditor | null > ( null ) ;
25- const monacoEl = useRef < HTMLDivElement > ( null ) ;
25+ const monacoInstance = useMonaco ( ) ;
26+ const providerRef = useRef < IDisposable | null > ( null ) ;
2627
2728 const { data : autoCompleteData } = useQuery ( {
2829 queryKey : [ "autocomplete" ] ,
2930 queryFn : ( ) => fetchAutocomplete ( ) ,
3031 } ) ;
3132
32- // biome-ignore lint: lint/correctness/useExhaustiveDependencies: the fix doesn't work
33+ // Configure Monaco
3334 useEffect ( ( ) => {
34- if ( monacoEl ) {
35- setEditor ( ( editor ) => {
36- if ( editor ) return editor ;
37-
38- monaco . languages . register ( { id : ID_LANGUAGE_SQL } ) ;
39- monaco . languages . setLanguageConfiguration (
40- ID_LANGUAGE_SQL ,
41- COMMAND_CONFIG ,
42- ) ;
43-
44- monaco . editor . defineTheme ( "sql-dark" , vsPlusTheme . darkThemeData ) ;
45- monaco . editor . defineTheme ( "sql-light" , vsPlusTheme . lightThemeData ) ;
46-
47- const newEditor = monaco . editor . create ( monacoEl . current ! , {
48- value,
49- language : ID_LANGUAGE_SQL ,
50- minimap : {
51- enabled : false ,
52- } ,
53- fontSize : 20 ,
54- padding : {
55- top : 20 ,
56- bottom : 20 ,
57- } ,
58- fontFamily : "JetBrains Mono" ,
59- automaticLayout : true ,
60- readOnly : onChange === undefined ,
61- } ) ;
35+ if ( ! monacoInstance ) return ;
6236
63- newEditor . onDidChangeModelContent ( ( _ ) => {
64- onChange ?.( newEditor . getValue ( ) ) ;
65- } ) ;
66-
67- return newEditor ;
68- } ) ;
69- }
37+ monacoInstance . languages . register ( { id : ID_LANGUAGE_SQL } ) ;
38+ monacoInstance . languages . setLanguageConfiguration (
39+ ID_LANGUAGE_SQL ,
40+ COMMAND_CONFIG
41+ ) ;
7042
71- return ( ) => editor ?. dispose ( ) ;
72- } , [ monacoEl . current ] ) ;
43+ monacoInstance . editor . defineTheme ( "sql-dark" , vsPlusTheme . darkThemeData ) ;
44+ monacoInstance . editor . defineTheme ( "sql-light" , vsPlusTheme . lightThemeData ) ;
45+ } , [ monacoInstance ] ) ;
7346
47+ // Register completion provider
7448 useEffect ( ( ) => {
75- if ( ! autoCompleteData ) return ;
76-
77- monaco . languages . registerCompletionItemProvider ( ID_LANGUAGE_SQL , {
78- provideCompletionItems : ( model , position ) => {
79- const word = model . getWordUntilPosition ( position ) ;
80- const range = {
81- startLineNumber : position . lineNumber ,
82- endLineNumber : position . lineNumber ,
83- startColumn : word . startColumn ,
84- endColumn : word . endColumn ,
85- } ;
86- const { suggestions } = autoSuggestionCompletionItems ( range ) ;
87-
88- const tableColumnSuggestions = autoCompleteData . tables . reduce (
89- (
90- acc : {
91- label : string ;
92- kind : monaco . languages . CompletionItemKind ;
93- insertText : string ;
94- range : {
95- startLineNumber : number ;
96- endLineNumber : number ;
97- startColumn : number ;
98- endColumn : number ;
49+ if ( ! monacoInstance || ! autoCompleteData ) return ;
50+
51+ providerRef . current ?. dispose ( ) ;
52+
53+ providerRef . current =
54+ monacoInstance . languages . registerCompletionItemProvider ( ID_LANGUAGE_SQL , {
55+ provideCompletionItems : ( model , position ) => {
56+ const word = model . getWordUntilPosition ( position ) ;
57+ const range = {
58+ startLineNumber : position . lineNumber ,
59+ endLineNumber : position . lineNumber ,
60+ startColumn : word . startColumn ,
61+ endColumn : word . endColumn ,
62+ } ;
63+ const { suggestions } = autoSuggestionCompletionItems ( range ) ;
64+
65+ const tableColumnSuggestions = autoCompleteData . tables . flatMap (
66+ ( { table_name, columns } ) => {
67+ const alias = table_name . substring ( 0 , 3 ) ;
68+
69+ const table = {
70+ label : table_name ,
71+ kind : monacoInstance . languages . CompletionItemKind . Variable ,
72+ insertText : table_name ,
73+ range,
9974 } ;
100- } [ ] ,
101- { table_name, columns } ,
102- ) => {
103- const alias = table_name . substring ( 0 , 3 ) ;
104-
105- const table = {
106- label : table_name ,
107- kind : monaco . languages . CompletionItemKind . Variable ,
108- insertText : table_name ,
109- range,
110- } ;
111-
112- const aliasTable = {
113- label : `${ table_name } AS ${ alias } ` ,
114- kind : monaco . languages . CompletionItemKind . Variable ,
115- insertText : `${ table_name } AS ${ alias } ` ,
116- range,
117- } ;
118-
119- const col = columns . map ( ( column ) => ( {
120- label : column ,
121- kind : monaco . languages . CompletionItemKind . Variable ,
122- insertText : column ,
123- range,
124- } ) ) ;
125-
126- const tableColumn = columns . map ( ( column ) => ( {
127- label : `${ table_name } .${ column } ` ,
128- kind : monaco . languages . CompletionItemKind . Variable ,
129- insertText : `${ table_name } .${ column } ` ,
130- range,
131- } ) ) ;
132-
133- const tableColumnAlias = columns . map ( ( column ) => ( {
134- label : `${ alias } .${ column } ` ,
135- kind : monaco . languages . CompletionItemKind . Variable ,
136- insertText : `${ alias } .${ column } ` ,
137- range,
138- } ) ) ;
139-
140- return [
141- ...acc ,
142- table ,
143- aliasTable ,
144- ...col ,
145- ...tableColumn ,
146- ...tableColumnAlias ,
147- ] ;
148- } ,
149- [ ] ,
150- ) ;
151-
152- return { suggestions : [ ...suggestions , ...tableColumnSuggestions ] } ;
153- } ,
154- } ) ;
155- } , [ autoCompleteData ] ) ;
15675
157- useEffect ( ( ) => {
158- if ( monacoEl . current ) {
159- monaco . editor . setTheme (
160- currentTheme === "light" ? "sql-light" : "sql-dark" ,
161- ) ;
162- }
163- } , [ currentTheme ] ) ;
76+ const aliasTable = {
77+ label : `${ table_name } AS ${ alias } ` ,
78+ kind : monacoInstance . languages . CompletionItemKind . Variable ,
79+ insertText : `${ table_name } AS ${ alias } ` ,
80+ range,
81+ } ;
82+
83+ const col = columns . map ( ( column ) => ( {
84+ label : column ,
85+ kind : monacoInstance . languages . CompletionItemKind . Variable ,
86+ insertText : column ,
87+ range,
88+ } ) ) ;
89+
90+ const tableColumn = columns . map ( ( column ) => ( {
91+ label : `${ table_name } .${ column } ` ,
92+ kind : monacoInstance . languages . CompletionItemKind . Variable ,
93+ insertText : `${ table_name } .${ column } ` ,
94+ range,
95+ } ) ) ;
96+
97+ const tableColumnAlias = columns . map ( ( column ) => ( {
98+ label : `${ alias } .${ column } ` ,
99+ kind : monacoInstance . languages . CompletionItemKind . Variable ,
100+ insertText : `${ alias } .${ column } ` ,
101+ range,
102+ } ) ) ;
103+
104+ return [
105+ table ,
106+ aliasTable ,
107+ ...col ,
108+ ...tableColumn ,
109+ ...tableColumnAlias ,
110+ ] ;
111+ }
112+ ) ;
113+
114+ return { suggestions : [ ...suggestions , ...tableColumnSuggestions ] } ;
115+ } ,
116+ } ) ;
117+
118+ return ( ) => {
119+ providerRef . current ?. dispose ( ) ;
120+ providerRef . current = null ;
121+ } ;
122+ } , [ monacoInstance , autoCompleteData ] ) ;
123+
124+ // Avoid rendering until theme is known
125+ if ( ! currentTheme ) return null ;
164126
165127 return (
166128 < Card className = "p-2" >
167- < div className = "w-full h-[200px]" ref = { monacoEl } />
129+ < EditorComponent
130+ height = "200px"
131+ value = { value }
132+ onChange = { ( val ) => onChange ?.( val ?? "" ) }
133+ language = { ID_LANGUAGE_SQL }
134+ theme = { currentTheme === "light" ? "sql-light" : "sql-dark" }
135+ options = { {
136+ minimap : { enabled : false } ,
137+ fontSize : 20 ,
138+ fontFamily : "JetBrains Mono" ,
139+ padding : { top : 20 , bottom : 20 } ,
140+ automaticLayout : true ,
141+ readOnly : onChange === undefined ,
142+ } }
143+ />
168144 </ Card >
169145 ) ;
170146} ;
0 commit comments