Skip to content

Commit

Permalink
feat: run current and run all with cursor line number and column numb…
Browse files Browse the repository at this point in the history
…er (#9)

* feat: add insert value menu

* feat: run current and run all
  • Loading branch information
invisal authored Feb 20, 2024
1 parent 475a152 commit db5d6c9
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 78 deletions.
25 changes: 14 additions & 11 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"cmdk": "^0.2.0",
"dbgate-query-splitter": "^4.9.3",
"eslint-plugin-jest": "^27.6.3",
"lucide-react": "^0.309.0",
"next": "14.0.4",
"react": "^18",
"react-dom": "^18",
"react-resizable-panels": "^1.0.9",
"sql-query-identifier": "^2.6.0",
"tailwind-merge": "^2.2.0",
"tailwindcss-animate": "^1.0.7"
},
Expand Down
79 changes: 57 additions & 22 deletions src/app/(windows)/QueryWindow.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useState } from "react";
import { splitQuery, sqliteSplitterOptions } from "dbgate-query-splitter";
import { useRef, useState } from "react";
import { identify } from "sql-query-identifier";
import { LucidePlay } from "lucide-react";
import SqlEditor from "@/components/SqlEditor";
import {
Expand All @@ -16,46 +16,71 @@ import { MultipleQueryProgress, multipleQuery } from "@/lib/multiple-query";
import QueryProgressLog from "../(components)/QueryProgressLog";
import OptimizeTableState from "../(components)/OptimizeTable/OptimizeTableState";
import { KEY_BINDING } from "@/lib/key-matcher";
import { ReactCodeMirrorRef } from "@uiw/react-codemirror";
import { selectStatementFromPosition } from "@/lib/sql-helper";

export default function QueryWindow() {
const { schema } = useAutoComplete();
const { databaseDriver } = useDatabaseDriver();
const [code, setCode] = useState("");
const [data, setData] = useState<OptimizeTableState>();
const [progress, setProgress] = useState<MultipleQueryProgress>();
const editorRef = useRef<ReactCodeMirrorRef>(null);
const [lineNumber, setLineNumber] = useState(0);
const [columnNumber, setColumnNumber] = useState(0);

const onRunClicked = () => {
const statements = splitQuery(code, {
...sqliteSplitterOptions,
adaptiveGoSplit: true,
}).map((statement) => statement.toString());
const onRunClicked = (all = false) => {
const statements = identify(code, {
dialect: "sqlite",
strict: false,
});

// Reset the result and make a new query
setData(undefined);
setProgress(undefined);
let finalStatements: string[] = [];

multipleQuery(databaseDriver, statements, (currentProgrss) => {
setProgress(currentProgrss);
})
.then(({ last }) => {
if (last) {
setData(OptimizeTableState.createFromResult(last));
}
const editor = editorRef.current;

if (all) {
finalStatements = statements.map((s) => s.text);
} else if (editor && editor.view) {
const position = editor.view.state.selection.main.head;
const statement = selectStatementFromPosition(statements, position);

if (statement) {
finalStatements = [statement.text];
}
}

if (finalStatements.length > 0) {
// Reset the result and make a new query
setData(undefined);
setProgress(undefined);

multipleQuery(databaseDriver, finalStatements, (currentProgrss) => {
setProgress(currentProgrss);
})
.catch(console.error);
.then(({ last }) => {
if (last) {
setData(OptimizeTableState.createFromResult(last));
}
})
.catch(console.error);
}
};

console.log(KEY_BINDING.run.toCodeMirrorKey());

return (
<ResizablePanelGroup direction="vertical">
<ResizablePanel style={{ position: "relative" }}>
<div className="absolute left-0 right-0 top-0 bottom-0 flex flex-col">
<div className="flex-grow overflow-hidden">
<SqlEditor
ref={editorRef}
value={code}
onChange={setCode}
schema={schema}
onCursorChange={(_, line, col) => {
setLineNumber(line);
setColumnNumber(col);
}}
onKeyDown={(e) => {
if (KEY_BINDING.run.match(e)) {
onRunClicked();
Expand All @@ -67,13 +92,23 @@ export default function QueryWindow() {
<div className="flex-grow-0 flex-shrink-0">
<Separator />
<div className="flex gap-1 p-1">
<Button variant={"ghost"} onClick={onRunClicked}>
<Button variant={"ghost"} onClick={() => onRunClicked()}>
<LucidePlay className="w-4 h-4 mr-2" />
Run{" "}
Run Current{" "}
<span className="text-xs ml-2 px-2 bg-secondary py-1 rounded">
{KEY_BINDING.run.toString()}
</span>
</Button>

<Button variant={"ghost"} onClick={() => onRunClicked(true)}>
<LucidePlay className="w-4 h-4 mr-2" />
Run All
</Button>

<div className="grow justify-end items-center flex text-sm mr-2 gap-2">
<div>Ln {lineNumber}</div>
<div>Col {columnNumber + 1}</div>
</div>
</div>
</div>
</div>
Expand Down
106 changes: 62 additions & 44 deletions src/components/SqlEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { tags as t } from "@lezer/highlight";
import { createTheme } from "@uiw/codemirror-themes";
import CodeMirror from "@uiw/react-codemirror";
import CodeMirror, {
EditorView,
ReactCodeMirrorRef,
} from "@uiw/react-codemirror";
import { sql, SQLite } from "@codemirror/lang-sql";
import { KeyboardEventHandler, useMemo } from "react";
import { forwardRef, KeyboardEventHandler, useMemo } from "react";

import { defaultKeymap } from "@codemirror/commands";
import { keymap } from "@codemirror/view";
Expand Down Expand Up @@ -45,48 +48,63 @@ interface SqlEditorProps {
onChange: (value: string) => void;
schema?: Record<string, string[]>;
onKeyDown?: KeyboardEventHandler<HTMLDivElement>;
onCursorChange?: (
pos: number,
lineNumber: number,
columnNumber: number
) => void;
}

export default function SqlEditor({
value,
onChange,
schema,
onKeyDown,
}: SqlEditorProps) {
const keyExtensions = useMemo(() => {
return keymap.of([
{
key: KEY_BINDING.run.toCodeMirrorKey(),
preventDefault: true,
run: () => true,
},
...defaultKeymap,
]);
}, []);
const SqlEditor = forwardRef<ReactCodeMirrorRef, SqlEditorProps>(
function SqlEditor(
{ value, onChange, schema, onKeyDown, onCursorChange }: SqlEditorProps,
ref
) {
const keyExtensions = useMemo(() => {
return keymap.of([
{
key: KEY_BINDING.run.toCodeMirrorKey(),
preventDefault: true,
run: () => true,
},
...defaultKeymap,
]);
}, []);

return (
<CodeMirror
autoFocus
onKeyDown={onKeyDown}
basicSetup={{
defaultKeymap: false,
drawSelection: false,
}}
theme={theme}
value={value}
height="100%"
onChange={onChange}
style={{
fontSize: 20,
height: "100%",
}}
extensions={[
keyExtensions,
sql({
dialect: SQLite,
schema,
}),
]}
/>
);
}
return (
<CodeMirror
ref={ref}
autoFocus
onKeyDown={onKeyDown}
basicSetup={{
defaultKeymap: false,
drawSelection: false,
}}
theme={theme}
value={value}
height="100%"
onChange={onChange}
style={{
fontSize: 20,
height: "100%",
}}
extensions={[
keyExtensions,
sql({
dialect: SQLite,
schema,
}),
EditorView.updateListener.of((state) => {
const pos = state.state.selection.main.head;
const line = state.state.doc.lineAt(pos);
const lineNumber = line.number;
const columnNumber = pos - line.from;
if (onCursorChange) onCursorChange(pos, lineNumber, columnNumber);
}),
]}
/>
);
}
);

export default SqlEditor;
28 changes: 28 additions & 0 deletions src/lib/sql-helper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import {
generateDeleteStatement,
generateInsertStatement,
generateUpdateStatement,
selectStatementFromPosition,
unescapeIdentity,
} from "./sql-helper";
import { identify } from "sql-query-identifier";

describe("Escape SQL", () => {
it("escape sql string", () => {
Expand Down Expand Up @@ -136,3 +138,29 @@ describe("Mapping sqlite column type to our table type", () => {
expect(convertSqliteType(type)).toBe(TableColumnDataType.REAL));
}
});

function ss(sql: string) {
const pos = sql.indexOf("|");
const statements = identify(sql.replace("|", ""));
return selectStatementFromPosition(statements, pos);
}

describe("Select current query", () => {
it("select current query", () => {
expect(ss("select * from |t1; update t1 set name='visal';")?.text).toBe(
"select * from t1;"
);

expect(ss("select * from t1|; update t1 set name='visal';")?.text).toBe(
"select * from t1;"
);

expect(ss("select * from t1;| update t1 set name='visal';")?.text).toBe(
"select * from t1;"
);

expect(ss("select * from t1; update| t1 set name='visal';")?.text).toBe(
"update t1 set name='visal';"
);
});
});
11 changes: 11 additions & 0 deletions src/lib/sql-helper.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { TableColumnDataType } from "@/app/(components)/OptimizeTable";
import { hex } from "./bit-operation";
import { IdentifyResult } from "sql-query-identifier/lib/defines";

export function escapeIdentity(str: string) {
return `"${str.replace(/"/g, `""`)}"`;
Expand Down Expand Up @@ -123,3 +124,13 @@ export function generateUpdateStatement(
tableName
)} SET ${setPart} WHERE ${wherePart};`;
}

export function selectStatementFromPosition(
statements: IdentifyResult[],
pos: number
): IdentifyResult | undefined {
for (const statement of statements) {
if (statement.end + 1 >= pos) return statement;
}
return undefined;
}

0 comments on commit db5d6c9

Please sign in to comment.