diff --git a/src/components/gui/sql-editor/index.tsx b/src/components/gui/sql-editor/index.tsx index 5d8b52a..f07f685 100644 --- a/src/components/gui/sql-editor/index.tsx +++ b/src/components/gui/sql-editor/index.tsx @@ -19,6 +19,7 @@ import { sqliteDialect } from "@/drivers/sqlite/sqlite-dialect"; import { functionTooltip } from "./function-tooltips"; import sqliteFunctionList from "@/drivers/sqlite/function-tooltip.json"; import { toast } from "sonner"; +import SqlStatementHighlightPlugin from "./statement-highlight"; interface SqlEditorProps { value: string; @@ -58,6 +59,15 @@ const SqlEditor = forwardRef( return createSQLTableNameHighlightPlugin([]); }, [schema]); + const baseTheme = useMemo(() => { + return EditorView.baseTheme({ + "& .cm-line": { + borderLeft: "3px solid transparent", + paddingLeft: "10px", + }, + }); + }, []); + const keyExtensions = useMemo(() => { return keymap.of([ { @@ -138,6 +148,7 @@ const SqlEditor = forwardRef( height: "100%", }} extensions={[ + baseTheme, keyExtensions, sql({ dialect: sqliteDialect, @@ -152,6 +163,7 @@ const SqlEditor = forwardRef( const columnNumber = pos - line.from; if (onCursorChange) onCursorChange(pos, lineNumber, columnNumber); }), + SqlStatementHighlightPlugin, ]} /> ); diff --git a/src/components/gui/sql-editor/statement-highlight.ts b/src/components/gui/sql-editor/statement-highlight.ts new file mode 100644 index 0000000..49891b2 --- /dev/null +++ b/src/components/gui/sql-editor/statement-highlight.ts @@ -0,0 +1,69 @@ +import { + Decoration, + EditorState, + EditorView, + StateField, + Range, +} from "@uiw/react-codemirror"; +import { syntaxTree } from "@codemirror/language"; +import { SyntaxNode } from "@lezer/common"; + +const statementLineHighlight = Decoration.line({ + class: "cm-highlight-statement", +}); + +function resolveToNearestStatement(node: SyntaxNode, cursor: number) { + const statements = node.getChildren("Statement"); + if (statements.length === 0) return null; + + for (let i = 0; i < statements.length; i++) { + const statement = statements[i]; + if (statement.to < cursor) continue; + return statement; + } + + return statements[statements.length - 1]; +} + +function getDecorationFromState(state: EditorState) { + const tree = syntaxTree(state); + const node = resolveToNearestStatement( + tree.topNode, + state.selection.main.from + ); + + if (!node) return Decoration.none; + + // Get the line of the node + const fromLineNumber = state.doc.lineAt(node.from).number; + const toLineNumber = state.doc.lineAt(node.to).number; + + const d: Range[] = []; + for (let i = fromLineNumber; i <= toLineNumber; i++) { + d.push(statementLineHighlight.range(state.doc.line(i).from)); + } + + return Decoration.set(d); +} + +const SqlStatementStateField = StateField.define({ + create(state) { + return getDecorationFromState(state); + }, + + update(_, tr) { + return getDecorationFromState(tr.state); + }, + + provide: (f) => EditorView.decorations.from(f), +}); + +const SqlStatementTheme = EditorView.baseTheme({ + ".cm-highlight-statement": { + borderLeft: "3px solid #ff9ff3 !important", + }, +}); + +const SqlStatementHighlightPlugin = [SqlStatementStateField, SqlStatementTheme]; + +export default SqlStatementHighlightPlugin;