feat: add SQL single quote caret pairing

This commit is contained in:
Spencer.Chang authored and GitHub committed 2026-06-23 21:14:15 +08:00
1 parent 0c8bf24977
commit aed2192d8c
4 files changed
+134

No files matched your search

@@ -350,6 +350,18 @@ function requestExecute() {
return true;
}
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,
}));
view.dispatch(transaction, { userEvent: "input.type" });
return true;
}
function executionPickerAnchor(currentView: EditorViewType, cursorPos: number, candidateCount: number): { left: number; top: number } | undefined {
const cursorRect = currentView.coordsAtPos(cursorPos);
const rootRect = editorRef.value?.getBoundingClientRect();
@@ -1933,6 +1945,7 @@ onMounted(async () => {
previewRangeComp.of(buildPreviewRangeExtension()),
Prec.highest(
keymap.of([
{ key: "'", run: handleSqlSingleQuote },
...closeBracketsKeymap,
{ key: "Tab", run: handleTab },
{
@@ -141,6 +141,7 @@ import { useSettingsStore } from "@/stores/settingsStore";
import type { DataGridSortDirection } from "@/lib/dataGridSort";
import { getTableMetadataCapabilities } from "@/lib/tableMetadataCapabilities";
import { forgetDataGridConditionHistory, loadDataGridConditionHistory, rememberDataGridConditionHistory } from "@/lib/dataGridConditionHistory";
import { caretPositionInsideInsertedSqlSingleQuotes, insertedSqlSingleQuoteAtCaret } from "@/lib/sqlQuoteCaret";
const SqlPreviewPanel = defineAsyncComponent(() => import("@/components/editor/SqlPreviewPanel.vue"));
@@ -453,6 +454,7 @@ const orderBySuggestionPosition = ref({ left: 0, top: 0 });
const orderByInput = ref(props.initialOrderByInput ?? "");
const hasOrderByInput = computed(() => orderByInput.value.trim().length > 0);
const whereFilterInput = ref(props.initialWhereInput ?? "");
let previousWhereFilterInputValue = whereFilterInput.value;
const hasWhereFilterInput = computed(() => whereFilterInput.value.trim().length > 0);
const conditionHistoryScope = computed(() => ({
connectionId: props.connectionId,
@@ -1327,9 +1329,38 @@ function deleteWhereHistorySuggestion(value: string) {
whereSuggestionIndex.value = whereSuggestions.value.length ? Math.min(whereSuggestionIndex.value, whereSuggestions.value.length - 1) : -1;
}
function onWhereFilterInput(event: Event) {
const input = event.target instanceof HTMLInputElement ? event.target : null;
if (!input) return;
const nextValue = input.value;
if (
insertedSqlSingleQuoteAtCaret({
previousValue: previousWhereFilterInputValue,
nextValue,
selectionStart: input.selectionStart,
})
) {
const caret = input.selectionStart ?? nextValue.length;
const pairedValue = `${nextValue.slice(0, caret)}'${nextValue.slice(caret)}`;
whereFilterInput.value = pairedValue;
previousWhereFilterInputValue = pairedValue;
nextTick(() => input.setSelectionRange(caret, caret));
return;
}
const nextCaret = caretPositionInsideInsertedSqlSingleQuotes({
previousValue: previousWhereFilterInputValue,
nextValue,
selectionStart: input.selectionStart,
});
previousWhereFilterInputValue = nextValue;
if (nextCaret == null) return;
nextTick(() => input.setSelectionRange(nextCaret, nextCaret));
}
watch(whereFilterInput, (val) => {
emit("update:whereInput", currentWhereInput() ?? "");
persistStructuredFilterState();
previousWhereFilterInputValue = val;
whereSuggestions.value = [];
if (!props.tableMeta?.columns?.length) return;
const trimmed = val.trim();
@@ -6603,6 +6634,7 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
spellcheck="false"
class="flex-1 h-5 min-w-0 text-xs bg-transparent outline-none placeholder:text-muted-foreground/60"
placeholder=""
@input="onWhereFilterInput"
@keydown="onWhereFilterKeydown"
@focus="showWhereHistorySuggestions"
@click="updateWhereSuggestionPosition"