perf(table-info): mark DDL search hits on the rendered DOM
This commit is contained in:
6 files changed
+172
-15
No files matched your search
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { applyDdlStoragePreference } from "@/lib/sql/ddlStorage";
|
||||
import { applyDdlSearchMarks } from "@/lib/sql/ddlSearchMarks";
|
||||
import DdlStorageToggle from "@/components/objects/DdlStorageToggle.vue";
|
||||
|
||||
import { useUpdateBlocker } from "@/lib/app/updatePreparation";
|
||||
@@ -10492,6 +10493,7 @@ watch([activeTableInfoTab, ddlLoading], ([tab, loading]) => {
|
||||
}
|
||||
void nextTick(() => {
|
||||
ddlPreRef.value?.focus();
|
||||
applyDdlSearchMarks(ddlPreRef.value, searchQuery.value);
|
||||
syncDdlSearchMatches(true);
|
||||
});
|
||||
});
|
||||
@@ -11471,24 +11473,27 @@ const filteredConstraints = computed(() => {
|
||||
return base.filter((c) => c.name.toLowerCase().includes(q) || c.constraint_type.toLowerCase().includes(q) || c.columns.some((col) => col.toLowerCase().includes(q)) || c.definition.toLowerCase().includes(q));
|
||||
});
|
||||
|
||||
const filteredDdlContent = computed(() => {
|
||||
if (!ddlContent.value) return "";
|
||||
const html = highlight(ddlContent.value);
|
||||
if (!searchQuery.value) return html;
|
||||
|
||||
const escaped = searchQuery.value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
const regex = new RegExp(`(${escaped})`, "gi");
|
||||
// Match only text between > and < (text nodes), then replace the search term within those spans
|
||||
return html.replace(/>([^<]*)</g, (_, text) => {
|
||||
return `>${text.replace(regex, '<mark class="ddl-search-match">$1</mark>')}<`;
|
||||
});
|
||||
});
|
||||
// The highlighted DDL only changes when the statement itself changes: the
|
||||
// search marks are applied to the rendered DOM instead, so typing in the DDL
|
||||
// search box no longer re-highlights (or re-parses) the whole document (#9212).
|
||||
const highlightedDdlContent = computed(() => (ddlContent.value ? highlight(ddlContent.value) : ""));
|
||||
|
||||
watch(
|
||||
[filteredDdlContent, searchQuery],
|
||||
highlightedDdlContent,
|
||||
async () => {
|
||||
await nextTick();
|
||||
applyDdlSearchMarks(ddlPreRef.value, searchQuery.value);
|
||||
syncDdlSearchMatches(true);
|
||||
},
|
||||
{ flush: "post" },
|
||||
);
|
||||
|
||||
watch(
|
||||
searchQuery,
|
||||
async () => {
|
||||
ddlSearchMatchIndex.value = 0;
|
||||
await nextTick();
|
||||
applyDdlSearchMarks(ddlPreRef.value, searchQuery.value);
|
||||
syncDdlSearchMatches(true);
|
||||
},
|
||||
{ flush: "post" },
|
||||
@@ -13616,7 +13621,7 @@ useUpdateBlocker(() => (hasPendingChanges.value || hasPendingDataEditorDraft.val
|
||||
tabindex="0"
|
||||
class="flex-1 min-w-0 text-xs font-mono p-3 overflow-auto ddl-code leading-5 select-text outline-none"
|
||||
:class="settingsStore.editorSettings.tableDdlWordWrap ? 'whitespace-pre-wrap break-words' : 'whitespace-pre'"
|
||||
v-html="filteredDdlContent"
|
||||
v-html="highlightedDdlContent"
|
||||
@keydown="onDdlKeydown"
|
||||
></pre>
|
||||
</template>
|
||||
|
||||
@@ -6,7 +6,18 @@ const metadataLoaderSource = readFileSync(new URL("../../../composables/useDataG
|
||||
|
||||
describe("DataGrid DDL search navigation", () => {
|
||||
it("resets navigation when the raw search query changes", () => {
|
||||
expect(dataGridSource).toMatch(/watch\(\s*\[filteredDdlContent, searchQuery\],/);
|
||||
expect(dataGridSource).toMatch(/watch\(\s*searchQuery,\s*async \(\) => \{\s*ddlSearchMatchIndex\.value = 0;/);
|
||||
});
|
||||
|
||||
it("highlights the DDL once and marks the rendered DOM for the search", () => {
|
||||
// #9212: re-highlighting (and re-parsing) the whole DDL on every keystroke
|
||||
// froze the window, so the highlighted HTML only depends on the DDL itself
|
||||
// and the search marks are applied to the already-rendered <pre>.
|
||||
expect(dataGridSource).toMatch(/const highlightedDdlContent = computed\(\(\) => \(ddlContent\.value \? highlight\(ddlContent\.value\) : ""\)\);/);
|
||||
expect(dataGridSource).toContain('v-html="highlightedDdlContent"');
|
||||
expect(dataGridSource).toContain('import { applyDdlSearchMarks } from "@/lib/sql/ddlSearchMarks"');
|
||||
expect(dataGridSource).not.toContain("filteredDdlContent");
|
||||
expect(dataGridSource.match(/applyDdlSearchMarks\(ddlPreRef\.value, searchQuery\.value\)/g) ?? []).toHaveLength(3);
|
||||
});
|
||||
|
||||
it("uses the shared DDL cache policy and exposes refresh controls", () => {
|
||||
|
||||
Reference in new issue
Block a user