feat(editor): add SQL compression

This commit is contained in:
gggaiitx authored and GitHub committed 2026-07-22 15:50:43 +08:00
1 parent ff46b7cdbb
commit 9602cc3329
13 files changed
+487 -3

No files matched your search

@@ -17,7 +17,7 @@ import { executableStatementRangeAtCursor, executableStatementRangeCacheForDoc,
import { currentStatementFrameRangeTo, visualSqlColumnsWithInlineHints } from "@/lib/sql/currentStatementFrame";
import { expandToSqlStatementWindow, parseInsertValueHints } from "@/lib/sql/insertValueHints";
import { insertValueHintColumnNames } from "@/lib/sql/insertValueHintColumns";
import { formatSqlText, type SqlFormatDialect } from "@/lib/sql/sqlFormatter";
import { formatSqlText, compressSqlText, type SqlFormatDialect } from "@/lib/sql/sqlFormatter";
import { blankLineDeletionChanges, replaceSelectedEditorText } from "@/lib/editor/queryEditorTextEdits";
import { buildSqlInConditionFromPasteSource, insertTextForSqlInCondition } from "@/lib/sql/sqlInListPaste";
import { resolveSqlSingleQuoteKeyAction } from "@/lib/sql/sqlQuoteCaret";
@@ -106,6 +106,7 @@ const props = defineProps<{
syntaxDialect?: "mysql" | "postgres" | "sqlserver";
formatDialect?: SqlFormatDialect;
formatRequestId?: number;
compressRequestId?: number;
executionError?: string;
executionErrorSql?: string;
readOnly?: boolean;
@@ -2112,6 +2113,30 @@ async function formatCurrentSql() {
}
}
function compressCurrentSql() {
if (props.readOnly) return;
const currentView = view.value;
if (!currentView) return;
const originalState = currentView.state;
const selection = originalState.selection.main;
const compressesSelection = !selection.empty;
const from = compressesSelection ? selection.from : 0;
const to = compressesSelection ? selection.to : originalState.doc.length;
const source = originalState.sliceDoc(from, to);
if (!source.trim()) return;
const compressed = compressSqlText(source, props.formatDialect ?? props.dialect ?? "generic");
if (currentView !== view.value || currentView.state !== originalState || currentView.state.sliceDoc(from, to) !== source) {
return;
}
if (compressed === source) return;
currentView.dispatch({
changes: { from, to, insert: compressed },
selection: compressesSelection ? { anchor: from, head: from + compressed.length } : { anchor: from + compressed.length },
});
}
function droppedTableReference(event: DragEvent) {
return activeTableReferencePayloadValue() ?? parseTableReferencePayload(event.dataTransfer?.getData(DBX_TABLE_REFERENCE_MIME));
}
@@ -3911,6 +3936,13 @@ watch(
},
);
watch(
() => props.compressRequestId,
(val, oldVal) => {
if (val && val !== oldVal) compressCurrentSql();
},
);
watch(
() => props.executionError,
() => {
@@ -129,6 +129,7 @@ const props = defineProps<{
executableSql: string;
activeOutputView: "result" | "summary" | "explain" | "chart";
formatSqlRequest: { id: number; tabId: string } | null;
compressSqlRequest: { id: number; tabId: string } | null;
selectedSql: string;
cursorPos: number;
blockDangerousRedisCommands: boolean;
@@ -833,6 +834,7 @@ defineExpose({ focusSearch, refreshData, refreshQueryEditorCompletionCache, hand
:syntax-dialect="editorSyntaxDialect"
:format-dialect="activeSqlFormatDialect"
:format-request-id="formatSqlRequest?.tabId === activeTab.id ? formatSqlRequest.id : undefined"
:compress-request-id="compressSqlRequest?.tabId === activeTab.id ? compressSqlRequest.id : undefined"
:execution-error="activeQueryError"
:execution-error-sql="activeTab.lastExecutedSql"
:statement-execution-markers="activeStatementExecutionMarkers"
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { computed, ref, watch, watchEffect } from "vue";
import { useI18n } from "vue-i18n";
import { Play, Loader2, Square, Database, Check, Table2, AlignLeft, GitBranch, Save, FolderOpen, Layers, X, Shield, Download, RotateCcw, AlertTriangle, ClipboardPaste } from "@lucide/vue";
import { Play, Loader2, Square, Database, Check, Table2, AlignLeft, GitBranch, Save, FolderOpen, Layers, X, Shield, Download, RotateCcw, AlertTriangle, ClipboardPaste, Minimize2 } from "@lucide/vue";
import { Button } from "@/components/ui/button";
import { SearchableSelect } from "@/components/ui/searchable-select";
import { Tooltip, TooltipTrigger, TooltipContent } from "@/components/ui/tooltip";
@@ -39,6 +39,7 @@ const emit = defineEmits<{
explain: [];
"update:explainMode": [mode: "explain" | "autotrace"];
formatSql: [];
compressSql: [];
toggleSqlKeywordCase: [];
saveSql: [];
openSql: [];
@@ -253,6 +254,14 @@ function databaseOptionIsProduction(database: string): boolean {
</TooltipTrigger>
<TooltipContent>{{ t("toolbar.formatSql") }}</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger as-child>
<Button variant="ghost" size="icon" class="h-6 w-6 text-amber-600 hover:bg-amber-500/10 hover:text-amber-700 dark:text-amber-300 dark:hover:text-amber-200" :disabled="activeTab.isExecuting || activeTab.isExplaining || !activeTab.sql.trim()" @click="emit('compressSql')">
<Minimize2 class="h-3.5 w-3.5" />
</Button>
</TooltipTrigger>
<TooltipContent>{{ t("toolbar.compressSql") }}</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger as-child>
<Button