mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-22 15:30:41 +00:00
fix(editor): skip right quote on repeated single-quote input
Co-authored-by: zipg <4047349+zipg@users.noreply.github.com>
This commit is contained in:
@@ -15,6 +15,7 @@ import { executableStatementRangeAtCursor, executableStatementRangeCacheForDoc,
|
||||
import { currentStatementFrameRangeTo, visualSqlColumns } from "@/lib/sql/currentStatementFrame";
|
||||
import { formatSqlText, type SqlFormatDialect } from "@/lib/sql/sqlFormatter";
|
||||
import { buildSqlInConditionFromPasteSource, insertTextForSqlInCondition } from "@/lib/sql/sqlInListPaste";
|
||||
import { resolveSqlSingleQuoteKeyAction } from "@/lib/sql/sqlQuoteCaret";
|
||||
import { formatMongoShellText } from "@/lib/mongo/mongoFormatter";
|
||||
import { useConnectionStore, COMPLETION_METADATA_CONCURRENCY } from "@/stores/connectionStore";
|
||||
import { useSettingsStore } from "@/stores/settingsStore";
|
||||
@@ -432,14 +433,28 @@ function requestExecuteFromView(currentView: EditorViewType, cursorPos: number,
|
||||
return true;
|
||||
}
|
||||
|
||||
function sqlSingleQuoteKeyActionAt(state: EditorViewType["state"], position: number) {
|
||||
return resolveSqlSingleQuoteKeyAction({
|
||||
previousChar: position > 0 ? state.doc.sliceString(position - 1, position) : "",
|
||||
nextChar: position < state.doc.length ? state.doc.sliceString(position, position + 1) : "",
|
||||
autoCloseBrackets: settingsStore.editorSettings.autoCloseBrackets,
|
||||
});
|
||||
}
|
||||
|
||||
function handleSqlSingleQuote(view: EditorViewType): boolean {
|
||||
const { state } = view;
|
||||
if (state.readOnly) return false;
|
||||
if (state.selection.ranges.some((range) => !range.empty || range.from === 0 || state.doc.sliceString(range.from - 1, range.from) !== "'")) return false;
|
||||
const transaction = state.changeByRange((range) => ({
|
||||
changes: { from: range.from, insert: "'" },
|
||||
range,
|
||||
}));
|
||||
const EditorSelection = codeMirrorEditorSelection;
|
||||
if (state.readOnly || !EditorSelection) return false;
|
||||
if (state.selection.ranges.some((range) => !range.empty)) return false;
|
||||
if (state.selection.ranges.some((range) => sqlSingleQuoteKeyActionAt(state, range.from) === "pass")) return false;
|
||||
const transaction = state.changeByRange((range) => {
|
||||
const nextRange = EditorSelection.cursor(range.from + 1);
|
||||
if (sqlSingleQuoteKeyActionAt(state, range.from) !== "insertEscapedQuote") return { range: nextRange };
|
||||
return {
|
||||
changes: { from: range.from, insert: "'" },
|
||||
range: nextRange,
|
||||
};
|
||||
});
|
||||
view.dispatch(transaction, { userEvent: "input.type" });
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolveSqlSingleQuoteKeyAction } from "@/lib/sql/sqlQuoteCaret";
|
||||
|
||||
describe("resolveSqlSingleQuoteKeyAction", () => {
|
||||
it("skips over the auto-inserted closing single quote", () => {
|
||||
expect(resolveSqlSingleQuoteKeyAction({ previousChar: "'", nextChar: "'" })).toBe("skipClosingQuote");
|
||||
});
|
||||
|
||||
it("keeps escaped quote insertion when there is no closing quote to skip", () => {
|
||||
expect(resolveSqlSingleQuoteKeyAction({ previousChar: "'", nextChar: "" })).toBe("insertEscapedQuote");
|
||||
});
|
||||
|
||||
it("lets CodeMirror handle ordinary single quote input", () => {
|
||||
expect(resolveSqlSingleQuoteKeyAction({ previousChar: "a", nextChar: "" })).toBe("pass");
|
||||
});
|
||||
|
||||
it("does not skip an existing string opening quote after whitespace", () => {
|
||||
expect(resolveSqlSingleQuoteKeyAction({ previousChar: " ", nextChar: "'" })).toBe("pass");
|
||||
});
|
||||
|
||||
it("does not skip an existing string opening quote at the start of the document", () => {
|
||||
expect(resolveSqlSingleQuoteKeyAction({ previousChar: "", nextChar: "'" })).toBe("pass");
|
||||
});
|
||||
|
||||
it("does not intercept input when auto-close brackets is disabled", () => {
|
||||
expect(resolveSqlSingleQuoteKeyAction({ previousChar: "'", nextChar: "'", autoCloseBrackets: false })).toBe("pass");
|
||||
});
|
||||
});
|
||||
@@ -4,6 +4,16 @@ export interface SqlSingleQuoteCaretOptions {
|
||||
selectionStart: number | null | undefined;
|
||||
}
|
||||
|
||||
export type SqlSingleQuoteKeyAction = "pass" | "skipClosingQuote" | "insertEscapedQuote";
|
||||
|
||||
export function resolveSqlSingleQuoteKeyAction(options: { previousChar?: string; nextChar?: string; autoCloseBrackets?: boolean }): SqlSingleQuoteKeyAction {
|
||||
const { previousChar = "", nextChar = "", autoCloseBrackets = true } = options;
|
||||
if (!autoCloseBrackets) return "pass";
|
||||
if (previousChar === "'" && nextChar === "'") return "skipClosingQuote";
|
||||
if (previousChar === "'") return "insertEscapedQuote";
|
||||
return "pass";
|
||||
}
|
||||
|
||||
export function insertedSqlSingleQuoteAtCaret(options: SqlSingleQuoteCaretOptions): boolean {
|
||||
const { previousValue, nextValue, selectionStart } = options;
|
||||
if (typeof selectionStart !== "number" || selectionStart < 1) return false;
|
||||
|
||||
Reference in New Issue
Block a user