diff --git a/apps/desktop/src/App.vue b/apps/desktop/src/App.vue index b7bf7ffb34..bcac82df6d 100644 --- a/apps/desktop/src/App.vue +++ b/apps/desktop/src/App.vue @@ -204,6 +204,7 @@ const contentAreaRef = ref | null>(null); const selectedSql = ref(""); const cursorPos = ref(0); const formatSqlRequest = ref<{ id: number; tabId: string } | null>(null); +const compressSqlRequest = ref<{ id: number; tabId: string } | null>(null); const activeOutputView = ref<"result" | "summary" | "explain" | "chart">("result"); const newQueryContextSource = ref<"tab" | "sidebar">("tab"); const queryEditorDdlTarget = ref<{ connectionId: string; database: string; catalog?: string; schema?: string; tableName: string; objectType?: ObjectSourceKind } | null>(null); @@ -668,6 +669,15 @@ function formatActiveSql() { }; } +function compressActiveSql() { + const tab = activeTab.value; + if (!tab || tab.mode !== "query" || !tab.sql.trim()) return; + compressSqlRequest.value = { + id: (compressSqlRequest.value?.id ?? 0) + 1, + tabId: tab.id, + }; +} + function toggleSqlKeywordCase() { const sqlFormatter = settingsStore.editorSettings.sqlFormatter; settingsStore.updateEditorSettings({ @@ -2177,6 +2187,7 @@ onUnmounted(() => { @cancel="cancelActiveExecution()" @explain="tryExplain()" @format-sql="formatActiveSql" + @compress-sql="compressActiveSql" @toggle-sql-keyword-case="toggleSqlKeywordCase" @save-sql="void openSaveSqlDialog()" @open-sql="openSqlFile" @@ -2197,6 +2208,7 @@ onUnmounted(() => { :executable-sql="executableSql" :active-output-view="activeOutputView" :format-sql-request="formatSqlRequest" + :compress-sql-request="compressSqlRequest" :selected-sql="selectedSql" :cursor-pos="cursorPos" :block-dangerous-redis-commands="blockDangerousRedisCommands" diff --git a/apps/desktop/src/components/editor/QueryEditor.vue b/apps/desktop/src/components/editor/QueryEditor.vue index a20cae334e..5687195b61 100644 --- a/apps/desktop/src/components/editor/QueryEditor.vue +++ b/apps/desktop/src/components/editor/QueryEditor.vue @@ -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, () => { diff --git a/apps/desktop/src/components/layout/ContentArea.vue b/apps/desktop/src/components/layout/ContentArea.vue index c44bb75f67..e84a142fd4 100644 --- a/apps/desktop/src/components/layout/ContentArea.vue +++ b/apps/desktop/src/components/layout/ContentArea.vue @@ -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" diff --git a/apps/desktop/src/components/layout/EditorToolbar.vue b/apps/desktop/src/components/layout/EditorToolbar.vue index eb90a716b3..21fe1a229c 100644 --- a/apps/desktop/src/components/layout/EditorToolbar.vue +++ b/apps/desktop/src/components/layout/EditorToolbar.vue @@ -1,7 +1,7 @@