From 77a59437290e81b9cc51c171ae84fee96c3ddffc Mon Sep 17 00:00:00 2001 From: zipg Date: Fri, 10 Jul 2026 12:27:41 +0800 Subject: [PATCH] fix(editor): update SQL search escaping and match counts --- .../components/editor/EditorSearchPanel.vue | 35 +++++++++++++++---- .../src/components/editor/QueryEditor.vue | 1 + .../editor/editorSearchQuery.spec.ts | 28 +++++++++++++++ .../src/lib/editor/editorSearchQuery.ts | 18 ++++++++++ 4 files changed, 75 insertions(+), 7 deletions(-) create mode 100644 apps/desktop/src/lib/__tests__/editor/editorSearchQuery.spec.ts create mode 100644 apps/desktop/src/lib/editor/editorSearchQuery.ts diff --git a/apps/desktop/src/components/editor/EditorSearchPanel.vue b/apps/desktop/src/components/editor/EditorSearchPanel.vue index e4e6d5fdb..23b237185 100644 --- a/apps/desktop/src/components/editor/EditorSearchPanel.vue +++ b/apps/desktop/src/components/editor/EditorSearchPanel.vue @@ -3,8 +3,9 @@ import { ref, nextTick, onBeforeUnmount, watch } from "vue"; import { useI18n } from "vue-i18n"; import type { EditorView } from "@codemirror/view"; import { EditorSelection } from "@codemirror/state"; -import { SearchQuery, setSearchQuery, openSearchPanel as cmOpenSearchPanel, findNext as cmFindNext, findPrevious as cmFindPrevious, replaceNext as cmReplaceNext, replaceAll as cmReplaceAll } from "@codemirror/search"; +import { setSearchQuery, openSearchPanel as cmOpenSearchPanel, findNext as cmFindNext, findPrevious as cmFindPrevious, replaceNext as cmReplaceNext, replaceAll as cmReplaceAll } from "@codemirror/search"; import { ChevronUp, ChevronDown, ChevronRight, X } from "@lucide/vue"; +import { createEditorSearchQuery } from "@/lib/editor/editorSearchQuery"; const props = defineProps<{ view: EditorView | null; @@ -26,17 +27,25 @@ const replaceInputRef = ref(); const matchCountLimited = ref(false); const SEARCH_UPDATE_DELAY_MS = 120; +const DOCUMENT_SEARCH_UPDATE_DELAY_MS = 500; const MATCH_COUNT_LIMIT = 1000; let searchUpdateTimer: ReturnType | null = null; +let documentSearchUpdateTimer: ReturnType | null = null; + +function clearDocumentSearchUpdate() { + if (!documentSearchUpdateTimer) return; + clearTimeout(documentSearchUpdateTimer); + documentSearchUpdateTimer = null; +} function dispatchSearchQuery() { const v = props.view; if (!v) return; - const q = new SearchQuery({ + const q = createEditorSearchQuery({ search: searchText.value, caseSensitive: caseSensitive.value, - regexp: useRegex.value, + useRegex: useRegex.value, replace: replaceText.value, }); v.dispatch({ effects: setSearchQuery.of(q) }); @@ -48,7 +57,7 @@ function clearSearchQuery() { const selection = v.state.selection.main; v.dispatch({ selection: EditorSelection.single(selection.head), - effects: setSearchQuery.of(new SearchQuery({ search: "" })), + effects: setSearchQuery.of(createEditorSearchQuery({ search: "", caseSensitive: false, useRegex: false })), }); matchCount.value = 0; currentMatchIndex.value = 0; @@ -64,10 +73,10 @@ function updateMatchInfo(autoSelect = false) { return; } try { - const q = new SearchQuery({ + const q = createEditorSearchQuery({ search: searchText.value, caseSensitive: caseSensitive.value, - regexp: useRegex.value, + useRegex: useRegex.value, }); if (!q.valid) { matchCount.value = 0; @@ -101,6 +110,7 @@ function updateMatchInfo(autoSelect = false) { } function scheduleSearchUpdate(autoSelect = false) { + clearDocumentSearchUpdate(); if (searchUpdateTimer) { clearTimeout(searchUpdateTimer); searchUpdateTimer = null; @@ -116,6 +126,15 @@ function scheduleSearchUpdate(autoSelect = false) { }, SEARCH_UPDATE_DELAY_MS); } +function scheduleDocumentSearchUpdate() { + if (!searchVisible.value || !searchText.value) return; + clearDocumentSearchUpdate(); + documentSearchUpdateTimer = setTimeout(() => { + documentSearchUpdateTimer = null; + updateMatchInfo(); + }, DOCUMENT_SEARCH_UPDATE_DELAY_MS); +} + function openSearch(): boolean { searchVisible.value = true; const v = props.view; @@ -146,6 +165,7 @@ function closeSearch() { const wasVisible = searchVisible.value; searchVisible.value = false; showReplace.value = false; + clearDocumentSearchUpdate(); const v = props.view; if (v) { clearSearchQuery(); @@ -204,13 +224,14 @@ watch(replaceText, () => { }); onBeforeUnmount(() => { + clearDocumentSearchUpdate(); if (searchUpdateTimer) { clearTimeout(searchUpdateTimer); searchUpdateTimer = null; } }); -defineExpose({ openSearch, openReplace, closeSearch }); +defineExpose({ openSearch, openReplace, closeSearch, scheduleDocumentSearchUpdate });