feat(editor): format SQL files on save
This commit is contained in:
1 parent
249896dddf
commit
876f850e2d
17 files changed
+148
-7
No files matched your search
@@ -119,7 +119,11 @@ import { initSavedSqlEditorPositions } from "@/lib/app/savedSqlEditorPosition";
|
||||
import { hasTreeNodeDatabaseContext } from "@/lib/sidebar/treeNodeContext";
|
||||
import { isSchemaAware, isSingleDatabase, usesTreeSchemaMode } from "@/lib/database/databaseFeatureSupport";
|
||||
import { codeMirrorSqlDialect, connectionUsesDatabaseObjectTreeMode, effectiveDatabaseTypeForConnection } from "@/lib/database/jdbcDialect";
|
||||
import { sqlFormatDialectForDbType } from "@/lib/sql/sqlFormatter";
|
||||
import { canFormatSqlForDatabaseType, formatSqlForEditing, sqlFormatDialectForDbType } from "@/lib/sql/sqlFormatter";
|
||||
import { formatSqlSnapshotForSave } from "@/lib/sql/sqlFormatOnSave";
|
||||
import { detectAndFormatStructured } from "@/lib/sql/autoFormat";
|
||||
import { formatMongoShellText } from "@/lib/mongo/mongoFormatter";
|
||||
import { detectAndFormatElasticsearchRequests } from "@/lib/elasticsearch/elasticsearchFormatter";
|
||||
import { detectDatabaseFileType } from "@/lib/database/databaseFileDetection";
|
||||
import { ensureJdbcxRuntimeDrivers } from "@/lib/database/jdbcxBuiltinDriver";
|
||||
import { ensureRegisteredJdbcProductRuntimeDrivers } from "@/lib/database/jdbcProductProfiles";
|
||||
@@ -1165,7 +1169,7 @@ function handleCloseActionPromptOpenChange(open: boolean) {
|
||||
async function writeExternalSqlTab(tab: QueryTab, options: { closeAfterSave?: boolean; expectedContentHash?: string; expectedMissing?: boolean } = {}): Promise<"saved" | "retry" | "failed"> {
|
||||
if (!tab.externalSqlPath || !isTauriRuntime()) return "failed";
|
||||
try {
|
||||
const result = await api.writeExternalSqlFile(tab.externalSqlPath, tab.sql, {
|
||||
const result = await api.writeExternalSqlFile(tab.externalSqlPath, await formattedSqlForSave(tab), {
|
||||
expectedContentHash: options.expectedContentHash,
|
||||
expectedMissing: options.expectedMissing,
|
||||
});
|
||||
@@ -1211,6 +1215,45 @@ function savedSqlTargetForSave(tab: QueryTab) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the "format SQL when saving SQL files" editor setting: returns the
|
||||
* formatted SQL that should be written to disk / the SQL library, mirroring the
|
||||
* editor's own formatting logic (Mongo shell, Elasticsearch, structured JSON/XML
|
||||
* and SQL) so a save never corrupts non-SQL content. When formatting changes the
|
||||
* SQL, the tab content is also updated so the editor reflects exactly what was
|
||||
* saved and stays clean if `markTabClean` runs afterwards.
|
||||
*/
|
||||
async function formattedSqlForSave(tab: QueryTab): Promise<string> {
|
||||
if (!settingsStore.editorSettings.formatSqlOnSqlFileSave) return tab.sql;
|
||||
const sqlSnapshot = tab.sql;
|
||||
if (!sqlSnapshot.trim()) return sqlSnapshot;
|
||||
const connection = connectionStore.getConfig(tab.connectionId);
|
||||
const databaseType = effectiveDatabaseTypeForConnection(connection) ?? connection?.db_type;
|
||||
if (!canFormatSqlForDatabaseType(databaseType)) return sqlSnapshot;
|
||||
try {
|
||||
return await formatSqlSnapshotForSave(
|
||||
sqlSnapshot,
|
||||
() => tab.sql,
|
||||
async (sql) => {
|
||||
if (databaseType === "mongodb") return formatMongoShellText(sql, settingsStore.editorSettings.sqlFormatter);
|
||||
const esRequest = detectAndFormatElasticsearchRequests(sql, databaseType, settingsStore.editorSettings.sqlFormatter.tabWidth);
|
||||
if (esRequest.kind === "elasticsearch") return esRequest.formatted;
|
||||
if (esRequest.kind === "unsupported") return sql;
|
||||
const structured = detectAndFormatStructured(sql, {
|
||||
indentSize: settingsStore.editorSettings.sqlFormatter.tabWidth,
|
||||
useTabs: settingsStore.editorSettings.sqlFormatter.useTabs,
|
||||
});
|
||||
if (structured.kind === "json" || structured.kind === "xml") return structured.formatted;
|
||||
if (structured.kind === "unsupported") return sql;
|
||||
return formatSqlForEditing(sql, sqlFormatDialectForDbType(databaseType), settingsStore.editorSettings.sqlFormatter);
|
||||
},
|
||||
(formatted) => queryStore.updateSql(tab.id, formatted),
|
||||
);
|
||||
} catch {
|
||||
return tab.sql;
|
||||
}
|
||||
}
|
||||
|
||||
async function saveTabForCloseAll(tabId: string): Promise<boolean> {
|
||||
const tab = queryStore.tabs.find((t) => t.id === tabId);
|
||||
if (!tab) return true;
|
||||
@@ -1237,7 +1280,7 @@ async function saveTabForCloseAll(tabId: string): Promise<boolean> {
|
||||
database: target.database,
|
||||
catalog: target.catalog,
|
||||
schema: target.schema,
|
||||
sql: tab.sql,
|
||||
sql: await formattedSqlForSave(tab),
|
||||
});
|
||||
queryStore.linkSavedSql(tab.id, saved.id, saved.name);
|
||||
queryStore.markTabClean(tab);
|
||||
@@ -1305,7 +1348,7 @@ async function handleSaveTab(tabId: string) {
|
||||
database: target.database,
|
||||
catalog: target.catalog,
|
||||
schema: target.schema,
|
||||
sql: tab.sql,
|
||||
sql: await formattedSqlForSave(tab),
|
||||
});
|
||||
queryStore.linkSavedSql(tab.id, updated.id, updated.name);
|
||||
queryStore.markTabClean(tab);
|
||||
@@ -1347,7 +1390,7 @@ async function openSaveSqlDialog() {
|
||||
database: target.database,
|
||||
catalog: target.catalog,
|
||||
schema: target.schema,
|
||||
sql: tab.sql,
|
||||
sql: await formattedSqlForSave(tab),
|
||||
});
|
||||
queryStore.linkSavedSql(tab.id, updated.id, updated.name);
|
||||
queryStore.markTabClean(tab);
|
||||
@@ -1446,7 +1489,7 @@ async function confirmSaveSqlToLibrary() {
|
||||
database: target.database,
|
||||
catalog: target.catalog,
|
||||
schema: target.schema,
|
||||
sql: tab.sql,
|
||||
sql: await formattedSqlForSave(tab),
|
||||
});
|
||||
queryStore.linkSavedSql(tab.id, saved.id, saved.name);
|
||||
queryStore.markTabClean(tab);
|
||||
@@ -1461,7 +1504,7 @@ async function confirmSaveSqlToLibrary() {
|
||||
async function saveExternalSqlTabAs(tab: QueryTab): Promise<boolean> {
|
||||
if (!canSaveSqlTab(tab) || !isTauriRuntime()) return false;
|
||||
try {
|
||||
const saved = await api.saveExternalSqlFile(defaultSavedSqlName(tab.title), tab.sql);
|
||||
const saved = await api.saveExternalSqlFile(defaultSavedSqlName(tab.title), await formattedSqlForSave(tab));
|
||||
if (!saved) return false;
|
||||
queryStore.linkExternalSqlPath(tab.id, saved.path, sqlFileTitleFromPath(saved.path), saved.version);
|
||||
rememberExternalSqlFileTarget(saved.path, { connectionId: tab.connectionId, database: tab.database, catalog: tab.catalog });
|
||||
|
||||
@@ -451,6 +451,7 @@ const editDataTabReuseMode = ref<DataTabReuseMode>(settingsStore.editorSettings.
|
||||
const editOpenDataTabsNextToActive = ref(settingsStore.editorSettings.openDataTabsNextToActive);
|
||||
const editPrefillNewQueryWithSelect = ref(settingsStore.editorSettings.prefillNewQueryWithSelect);
|
||||
const editGenerateSqlIncludeDatabaseName = ref(settingsStore.editorSettings.generateSqlIncludeDatabaseName);
|
||||
const editFormatSqlOnSqlFileSave = ref(settingsStore.editorSettings.formatSqlOnSqlFileSave);
|
||||
const editClickTableNavigationTarget = ref<ClickTableNavigationTarget>(settingsStore.editorSettings.clickTableNavigationTarget);
|
||||
const editUpdateNotificationsEnabled = ref(settingsStore.editorSettings.updateNotificationsEnabled);
|
||||
const editSidebarHiddenTablePrefixes = ref(settingsStore.editorSettings.sidebarHiddenTablePrefixes.join("\n"));
|
||||
@@ -583,6 +584,7 @@ function currentEditorSettingsDraft(): EditorSettingsDraft {
|
||||
openDataTabsNextToActive: editOpenDataTabsNextToActive.value,
|
||||
prefillNewQueryWithSelect: editPrefillNewQueryWithSelect.value,
|
||||
generateSqlIncludeDatabaseName: editGenerateSqlIncludeDatabaseName.value,
|
||||
formatSqlOnSqlFileSave: editFormatSqlOnSqlFileSave.value,
|
||||
updateNotificationsEnabled: editUpdateNotificationsEnabled.value,
|
||||
sidebarObjectInfoMode: editSidebarObjectInfoMode.value,
|
||||
sidebarAllowHorizontalScroll: editSidebarAllowHorizontalScroll.value,
|
||||
@@ -873,6 +875,7 @@ function syncEditorSettingsDraftFromStore() {
|
||||
editOpenDataTabsNextToActive.value = settingsStore.editorSettings.openDataTabsNextToActive;
|
||||
editPrefillNewQueryWithSelect.value = settingsStore.editorSettings.prefillNewQueryWithSelect;
|
||||
editGenerateSqlIncludeDatabaseName.value = settingsStore.editorSettings.generateSqlIncludeDatabaseName;
|
||||
editFormatSqlOnSqlFileSave.value = settingsStore.editorSettings.formatSqlOnSqlFileSave;
|
||||
editClickTableNavigationTarget.value = settingsStore.editorSettings.clickTableNavigationTarget;
|
||||
editUpdateNotificationsEnabled.value = settingsStore.editorSettings.updateNotificationsEnabled;
|
||||
editSidebarHiddenTablePrefixes.value = settingsStore.editorSettings.sidebarHiddenTablePrefixes.join("\n");
|
||||
@@ -1122,6 +1125,7 @@ function resetDefaultsForTab(tab: SettingsCategory) {
|
||||
editOpenDataTabsNextToActive.value = DEFAULT_EDITOR_SETTINGS.openDataTabsNextToActive;
|
||||
editPrefillNewQueryWithSelect.value = DEFAULT_EDITOR_SETTINGS.prefillNewQueryWithSelect;
|
||||
editGenerateSqlIncludeDatabaseName.value = DEFAULT_EDITOR_SETTINGS.generateSqlIncludeDatabaseName;
|
||||
editFormatSqlOnSqlFileSave.value = DEFAULT_EDITOR_SETTINGS.formatSqlOnSqlFileSave;
|
||||
editClickTableNavigationTarget.value = DEFAULT_EDITOR_SETTINGS.clickTableNavigationTarget;
|
||||
editUpdateNotificationsEnabled.value = DEFAULT_EDITOR_SETTINGS.updateNotificationsEnabled;
|
||||
editSidebarObjectInfoMode.value = DEFAULT_EDITOR_SETTINGS.sidebarObjectInfoMode;
|
||||
@@ -1248,6 +1252,7 @@ function resetAllDefaults() {
|
||||
editOpenDataTabsNextToActive.value = DEFAULT_EDITOR_SETTINGS.openDataTabsNextToActive;
|
||||
editPrefillNewQueryWithSelect.value = DEFAULT_EDITOR_SETTINGS.prefillNewQueryWithSelect;
|
||||
editGenerateSqlIncludeDatabaseName.value = DEFAULT_EDITOR_SETTINGS.generateSqlIncludeDatabaseName;
|
||||
editFormatSqlOnSqlFileSave.value = DEFAULT_EDITOR_SETTINGS.formatSqlOnSqlFileSave;
|
||||
editUpdateNotificationsEnabled.value = DEFAULT_EDITOR_SETTINGS.updateNotificationsEnabled;
|
||||
editSidebarObjectInfoMode.value = DEFAULT_EDITOR_SETTINGS.sidebarObjectInfoMode;
|
||||
editSidebarAllowHorizontalScroll.value = DEFAULT_EDITOR_SETTINGS.sidebarAllowHorizontalScroll;
|
||||
@@ -4208,6 +4213,16 @@ onUnmounted(() => {
|
||||
</div>
|
||||
<Switch id="generate-sql-include-database-name" v-model="editGenerateSqlIncludeDatabaseName" class="mt-0.5" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2">
|
||||
<div class="space-y-1">
|
||||
<Label for="format-sql-on-sql-file-save">{{ t("settings.formatSqlOnSqlFileSave") }}</Label>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
{{ t("settings.formatSqlOnSqlFileSaveDescription") }}
|
||||
</p>
|
||||
</div>
|
||||
<Switch id="format-sql-on-sql-file-save" v-model="editFormatSqlOnSqlFileSave" class="mt-0.5" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
@@ -6461,6 +6461,8 @@ export default {
|
||||
prefillNewQueryWithSelectDescription: "When creating a new query, prefill the editor with SELECT * FROM <table> based on the active table tab or the table selected in the sidebar.",
|
||||
generateSqlIncludeDatabaseName: "Include database name in generated SQL",
|
||||
generateSqlIncludeDatabaseNameDescription: "When supported by the database, generated table SQL uses database.table instead of only the table name.",
|
||||
formatSqlOnSqlFileSave: "Format SQL when saving SQL files",
|
||||
formatSqlOnSqlFileSaveDescription: "Automatically format SQL when saving a SQL file, including saved SQL library files and queries saved as .sql files.",
|
||||
clickTableNavigationTarget: "Ctrl+Click Table Opens DDL",
|
||||
clickTableNavigationTargetDescription: "When enabled, Ctrl/Cmd+clicking a table name opens the table structure editor (DDL). When disabled, it opens the table data view.",
|
||||
sqlVariableSyntax: "SQL variable & placeholder substitution",
|
||||
|
||||
@@ -6143,6 +6143,8 @@ export default withEnglishFallback({
|
||||
prefillNewQueryWithSelectDescription: "Al crear una nueva consulta, rellena el editor con SELECT * FROM <tabla> según la pestaña de tabla activa o la tabla seleccionada en la barra lateral.",
|
||||
generateSqlIncludeDatabaseName: "Incluir el nombre de la base de datos en el SQL generado",
|
||||
generateSqlIncludeDatabaseNameDescription: "Cuando la base de datos lo admite, el SQL de tabla generado usa base_de_datos.tabla en lugar de solo el nombre de la tabla.",
|
||||
formatSqlOnSqlFileSave: "Formatear SQL al guardar archivos SQL",
|
||||
formatSqlOnSqlFileSaveDescription: "Formatea automáticamente el SQL al guardar un archivo SQL, incluidos los archivos guardados en la biblioteca SQL y las consultas guardadas como archivos .sql.",
|
||||
clickTableNavigationTarget: "Ctrl+Clic abre DDL de tabla",
|
||||
clickTableNavigationTargetDescription: "Cuando está activado, Ctrl/Cmd+clic en el nombre de la tabla abre el editor de estructura (DDL). Cuando está desactivado, abre la vista de datos.",
|
||||
sqlVariableSyntax: "Sustitución de variables y marcadores SQL",
|
||||
|
||||
@@ -6143,6 +6143,8 @@ export default withEnglishFallback({
|
||||
prefillNewQueryWithSelectDescription: "Alla creazione di una nuova query, precompila l'editor con SELECT * FROM <tabella> in base alla scheda tabella attiva o alla tabella selezionata nella barra laterale.",
|
||||
generateSqlIncludeDatabaseName: "Includi il nome del database nell'SQL generato",
|
||||
generateSqlIncludeDatabaseNameDescription: "Quando supportato dal database, l'SQL della tabella generato usa database.tabella anziché solo il nome della tabella.",
|
||||
formatSqlOnSqlFileSave: "Formatta SQL durante il salvataggio dei file SQL",
|
||||
formatSqlOnSqlFileSaveDescription: "Formatta automaticamente l'SQL durante il salvataggio di un file SQL, inclusi i file salvati nella libreria SQL e le query salvate come file .sql.",
|
||||
clickTableNavigationTarget: "Ctrl+Clic apre DDL tabella",
|
||||
clickTableNavigationTargetDescription: "Quando attivato, Ctrl/Cmd+clic sul nome della tabella apre l'editor di struttura (DDL). Quando disattivato, apre la vista dati.",
|
||||
sqlVariableSyntax: "Sostituzione di variabili e segnaposto SQL",
|
||||
|
||||
@@ -6164,6 +6164,8 @@ export default withEnglishFallback({
|
||||
prefillNewQueryWithSelectDescription: "新規クエリ作成時、アクティブなテーブルタブまたはサイドバーで選択したテーブルに基づき、エディタに SELECT * FROM <テーブル名> を自動入力します。",
|
||||
generateSqlIncludeDatabaseName: "生成 SQL にデータベース名を含める",
|
||||
generateSqlIncludeDatabaseNameDescription: "データベースが対応している場合、生成するテーブル SQL でテーブル名のみではなく database.table を使用します。",
|
||||
formatSqlOnSqlFileSave: "SQL ファイル保存時に SQL をフォーマット",
|
||||
formatSqlOnSqlFileSaveDescription: "SQL ファイル保存時に SQL を自動フォーマットします。SQL ライブラリに保存されたファイルや、新規クエリを .sql ファイルとして保存する場合も含みます。",
|
||||
clickTableNavigationTarget: "Ctrl+クリックでテーブル構造を開く",
|
||||
clickTableNavigationTargetDescription: "有効にすると、Ctrl/Cmd+クリックでテーブル構造エディタ(DDL)を開きます。無効の場合はテーブルデータビューを開きます。",
|
||||
sqlVariableSyntax: "SQL 変数・プレースホルダー置換",
|
||||
|
||||
@@ -5846,6 +5846,8 @@ export default withEnglishFallback({
|
||||
prefillNewQueryWithSelectDescription: "새 쿼리를 만들 때 활성 테이블 탭이나 사이드바에서 선택한 테이블을 기반으로 편집기에 SELECT * FROM <table>을 미리 채웁니다.",
|
||||
generateSqlIncludeDatabaseName: "생성된 SQL에 데이터베이스 이름 포함",
|
||||
generateSqlIncludeDatabaseNameDescription: "데이터베이스가 지원하는 경우 생성된 테이블 SQL에서 테이블 이름만이 아니라 database.table을 사용합니다.",
|
||||
formatSqlOnSqlFileSave: "SQL 파일 저장 시 SQL 서식 지정",
|
||||
formatSqlOnSqlFileSaveDescription: "SQL 파일을 저장할 때 SQL을 자동으로 서식 지정합니다. SQL 라이브러리에 저장된 파일과 새 쿼리를 .sql 파일로 저장하는 경우도 포함합니다.",
|
||||
sqlVariableSyntax: "SQL 변수 및 자리표시자 치환",
|
||||
sqlVariableSyntaxDescription: "데이터베이스 유형별로 SQL을 실행하기 전에 DBX가 치환할 변수와 자리표시자 문법을 선택하세요. 모두 기본적으로 활성화됩니다.",
|
||||
sqlVariableSubstitutionEnabled: "치환 사용",
|
||||
|
||||
@@ -6145,6 +6145,8 @@ export default withEnglishFallback({
|
||||
prefillNewQueryWithSelectDescription: "Ao criar uma nova consulta, preenche o editor com SELECT * FROM <tabela> com base na aba de tabela ativa ou na tabela selecionada na barra lateral.",
|
||||
generateSqlIncludeDatabaseName: "Incluir nome do banco de dados no SQL gerado",
|
||||
generateSqlIncludeDatabaseNameDescription: "Quando compatível com o banco de dados, o SQL de tabela gerado usa banco.tabela em vez de somente o nome da tabela.",
|
||||
formatSqlOnSqlFileSave: "Formatar SQL ao salvar arquivos SQL",
|
||||
formatSqlOnSqlFileSaveDescription: "Formata automaticamente o SQL ao salvar um arquivo SQL, incluindo arquivos salvos na biblioteca SQL e consultas salvas como arquivos .sql.",
|
||||
clickTableNavigationTarget: "Ctrl+Clique abre DDL da tabela",
|
||||
clickTableNavigationTargetDescription: "Quando ativado, Ctrl/Cmd+clicar no nome da tabela abre o editor de estrutura (DDL). Quando desativado, abre a visualização de dados.",
|
||||
sqlVariableSyntax: "Substituição de variáveis e espaços reservados SQL",
|
||||
|
||||
@@ -6444,6 +6444,8 @@ export default withEnglishFallback({
|
||||
prefillNewQueryWithSelectDescription: "新建查询时,根据当前激活的数据表标签页或侧边栏选中的表,自动在编辑器中填充 SELECT * FROM <表名>。",
|
||||
generateSqlIncludeDatabaseName: "生成 SQL 时包含数据库名",
|
||||
generateSqlIncludeDatabaseNameDescription: "数据库支持时,生成的表 SQL 将使用“数据库名.表名”,而不只包含表名。",
|
||||
formatSqlOnSqlFileSave: "保存 SQL 文件时格式化 SQL",
|
||||
formatSqlOnSqlFileSaveDescription: "保存 SQL 文件时自动格式化 SQL,包括通过 SQL 库保存的文件以及将新查询保存为 .sql 文件等场景。",
|
||||
clickTableNavigationTarget: "Ctrl+点击表名打开 DDL",
|
||||
clickTableNavigationTargetDescription: "开启后,Ctrl/Cmd+点击表名将打开表结构编辑器(DDL);关闭则打开表数据视图。",
|
||||
sqlVariableSyntax: "SQL 变量与占位符替换",
|
||||
|
||||
@@ -5461,6 +5461,8 @@ export default withEnglishFallback({
|
||||
prefillNewQueryWithSelectDescription: "新建查詢時,根據目前啟用的資料表分頁或側邊欄選取的資料表,自動在編輯器中填入 SELECT * FROM <資料表名稱>。",
|
||||
generateSqlIncludeDatabaseName: "產生 SQL 時包含資料庫名稱",
|
||||
generateSqlIncludeDatabaseNameDescription: "資料庫支援時,產生的資料表 SQL 會使用「資料庫名稱.資料表名稱」,而非只包含資料表名稱。",
|
||||
formatSqlOnSqlFileSave: "儲存 SQL 檔案時格式化 SQL",
|
||||
formatSqlOnSqlFileSaveDescription: "儲存 SQL 檔案時自動格式化 SQL,包括透過 SQL 庫儲存的檔案,以及將新查詢儲存為 .sql 檔案等場景。",
|
||||
clickTableNavigationTarget: "Ctrl+點擊表名開啟 DDL",
|
||||
clickTableNavigationTargetDescription: "開啟後,Ctrl/Cmd+點擊表名將開啟表結構編輯器(DDL);關閉則開啟表資料檢視。",
|
||||
sqlVariableSyntax: "SQL 變數與佔位符替換",
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { formatSqlSnapshotForSave } from "@/lib/sql/sqlFormatOnSave";
|
||||
|
||||
function deferred<T>() {
|
||||
let resolve!: (value: T) => void;
|
||||
const promise = new Promise<T>((resolvePromise) => {
|
||||
resolve = resolvePromise;
|
||||
});
|
||||
return { promise, resolve };
|
||||
}
|
||||
|
||||
describe("formatSqlSnapshotForSave", () => {
|
||||
it("does not overwrite edits made while formatting loads", async () => {
|
||||
const formatting = deferred<string>();
|
||||
let sql = "select 1";
|
||||
const pending = formatSqlSnapshotForSave(
|
||||
sql,
|
||||
() => sql,
|
||||
() => formatting.promise,
|
||||
(formatted) => {
|
||||
sql = formatted;
|
||||
},
|
||||
);
|
||||
|
||||
sql = "select 2";
|
||||
formatting.resolve("SELECT 1");
|
||||
|
||||
await expect(pending).resolves.toBe("select 2");
|
||||
expect(sql).toBe("select 2");
|
||||
});
|
||||
|
||||
it("applies and returns formatted SQL when the snapshot is unchanged", async () => {
|
||||
let sql = "select 1";
|
||||
await expect(
|
||||
formatSqlSnapshotForSave(
|
||||
sql,
|
||||
() => sql,
|
||||
async () => "SELECT 1",
|
||||
(formatted) => {
|
||||
sql = formatted;
|
||||
},
|
||||
),
|
||||
).resolves.toBe("SELECT 1");
|
||||
expect(sql).toBe("SELECT 1");
|
||||
});
|
||||
});
|
||||
@@ -227,6 +227,7 @@ describe("settings search", () => {
|
||||
{ titleKey: "settings.autoAliasTables", category: "editor", targetId: "editor" },
|
||||
{ titleKey: "settings.clickTableNavigationTarget", category: "editor", targetId: "editor" },
|
||||
{ titleKey: "settings.generateSqlIncludeDatabaseName", category: "editor", targetId: "editor" },
|
||||
{ titleKey: "settings.formatSqlOnSqlFileSave", category: "editor", targetId: "editor" },
|
||||
{ titleKey: "settings.sqlFormatterKeywordCase", category: "formatter", targetId: "formatter" },
|
||||
{ titleKey: "settings.sqlFormatterFunctionCase", category: "formatter", targetId: "formatter" },
|
||||
{ titleKey: "settings.sqlFormatterDataTypeCase", category: "formatter", targetId: "formatter" },
|
||||
|
||||
@@ -68,6 +68,7 @@ export const EDITOR_SETTINGS_DRAFT_KEYS = [
|
||||
"openDataTabsNextToActive",
|
||||
"prefillNewQueryWithSelect",
|
||||
"generateSqlIncludeDatabaseName",
|
||||
"formatSqlOnSqlFileSave",
|
||||
"updateNotificationsEnabled",
|
||||
"sidebarObjectInfoMode",
|
||||
"sidebarAllowHorizontalScroll",
|
||||
|
||||
@@ -122,6 +122,7 @@ export const SETTINGS_SEARCH_DEFINITIONS: readonly SettingsSearchDefinition[] =
|
||||
{ id: "editor-app-close-unsaved-tabs", category: "editor", titleKey: "settings.appCloseUnsavedTabsMode", descriptionKey: "settings.appCloseUnsavedTabsModeDescription", targetId: "editor" },
|
||||
{ id: "editor-prefill-query", category: "editor", titleKey: "settings.prefillNewQueryWithSelect", descriptionKey: "settings.prefillNewQueryWithSelectDescription", targetId: "editor" },
|
||||
{ id: "editor-generate-sql-include-database", category: "editor", titleKey: "settings.generateSqlIncludeDatabaseName", descriptionKey: "settings.generateSqlIncludeDatabaseNameDescription", targetId: "editor" },
|
||||
{ id: "editor-format-sql-on-sql-file-save", category: "editor", titleKey: "settings.formatSqlOnSqlFileSave", descriptionKey: "settings.formatSqlOnSqlFileSaveDescription", targetId: "editor" },
|
||||
{ id: "editor-diagnostics", category: "editor", titleKey: "settings.sqlSemanticDiagnosticsEnabled", descriptionKey: "settings.sqlSemanticDiagnosticsEnabledDescription", targetId: "editor" },
|
||||
{ id: "editor-sql-variables", category: "editor", titleKey: "settings.sqlVariableSyntax", descriptionKey: "settings.sqlVariableSyntaxDescription", targetId: "editor" },
|
||||
{ id: "editor-saved-sql-target", category: "editor", titleKey: "settings.savedSqlOpenTarget", targetId: "editor" },
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
export async function formatSqlSnapshotForSave(sqlSnapshot: string, currentSql: () => string, format: (sql: string) => Promise<string>, applyFormatted: (sql: string) => void): Promise<string> {
|
||||
const formatted = await format(sqlSnapshot);
|
||||
if (currentSql() !== sqlSnapshot) return currentSql();
|
||||
if (formatted !== sqlSnapshot) applyFormatted(formatted);
|
||||
return formatted;
|
||||
}
|
||||
@@ -50,6 +50,14 @@ describe("normalizeEditorSettings", () => {
|
||||
expect(normalizeEditorSettings({ openDataTabsNextToActive: null } as any).openDataTabsNextToActive).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps SQL-file save formatting disabled unless explicitly enabled", () => {
|
||||
expect(normalizeEditorSettings({}).formatSqlOnSqlFileSave).toBe(false);
|
||||
expect(normalizeEditorSettings({ formatSqlOnSqlFileSave: true }).formatSqlOnSqlFileSave).toBe(true);
|
||||
expect(normalizeEditorSettings({ formatSqlOnSqlFileSave: false }).formatSqlOnSqlFileSave).toBe(false);
|
||||
expect(normalizeEditorSettings({ formatSqlOnSqlFileSave: "true" } as any).formatSqlOnSqlFileSave).toBe(false);
|
||||
expect(normalizeEditorSettings({ formatSqlOnSqlFileSave: null } as any).formatSqlOnSqlFileSave).toBe(false);
|
||||
});
|
||||
|
||||
it("defaults and bounds the regular expression match limit", () => {
|
||||
expect(normalizeEditorSettings({}).regexMaxMatchCount).toBe(1000);
|
||||
expect(normalizeEditorSettings({ regexMaxMatchCount: 2500 }).regexMaxMatchCount).toBe(2500);
|
||||
|
||||
@@ -604,6 +604,7 @@ export interface EditorSettings {
|
||||
openDataTabsNextToActive: boolean;
|
||||
prefillNewQueryWithSelect: boolean;
|
||||
generateSqlIncludeDatabaseName: boolean;
|
||||
formatSqlOnSqlFileSave: boolean;
|
||||
updateNotificationsEnabled: boolean;
|
||||
sidebarHiddenTablePrefixes: string[];
|
||||
sidebarObjectInfoMode: SidebarObjectInfoMode;
|
||||
@@ -813,6 +814,7 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = {
|
||||
openDataTabsNextToActive: false,
|
||||
prefillNewQueryWithSelect: true,
|
||||
generateSqlIncludeDatabaseName: false,
|
||||
formatSqlOnSqlFileSave: false,
|
||||
updateNotificationsEnabled: true,
|
||||
sidebarHiddenTablePrefixes: [],
|
||||
sidebarObjectInfoMode: "comment-inline",
|
||||
@@ -1208,6 +1210,7 @@ export function normalizeEditorSettings(settings: Partial<EditorSettings>, exist
|
||||
openDataTabsNextToActive: typeof settings.openDataTabsNextToActive === "boolean" ? settings.openDataTabsNextToActive : DEFAULT_EDITOR_SETTINGS.openDataTabsNextToActive,
|
||||
prefillNewQueryWithSelect: typeof settings.prefillNewQueryWithSelect === "boolean" ? settings.prefillNewQueryWithSelect : DEFAULT_EDITOR_SETTINGS.prefillNewQueryWithSelect,
|
||||
generateSqlIncludeDatabaseName: settings.generateSqlIncludeDatabaseName === true,
|
||||
formatSqlOnSqlFileSave: settings.formatSqlOnSqlFileSave === true,
|
||||
updateNotificationsEnabled: settings.updateNotificationsEnabled ?? DEFAULT_EDITOR_SETTINGS.updateNotificationsEnabled,
|
||||
sidebarHiddenTablePrefixes: normalizeSidebarHiddenTablePrefixes(settings.sidebarHiddenTablePrefixes),
|
||||
sidebarObjectInfoMode: normalizeSidebarObjectInfoMode(
|
||||
@@ -1800,6 +1803,7 @@ export const useSettingsStore = defineStore("settings", () => {
|
||||
if (partial.openDataTabsNextToActive !== undefined) editorSettings.value.openDataTabsNextToActive = partial.openDataTabsNextToActive === true;
|
||||
if (partial.prefillNewQueryWithSelect !== undefined) editorSettings.value.prefillNewQueryWithSelect = partial.prefillNewQueryWithSelect;
|
||||
if (partial.generateSqlIncludeDatabaseName !== undefined) editorSettings.value.generateSqlIncludeDatabaseName = partial.generateSqlIncludeDatabaseName === true;
|
||||
if (partial.formatSqlOnSqlFileSave !== undefined) editorSettings.value.formatSqlOnSqlFileSave = partial.formatSqlOnSqlFileSave === true;
|
||||
if (partial.updateNotificationsEnabled !== undefined) editorSettings.value.updateNotificationsEnabled = partial.updateNotificationsEnabled;
|
||||
if (partial.sidebarHiddenTablePrefixes !== undefined) editorSettings.value.sidebarHiddenTablePrefixes = normalizeSidebarHiddenTablePrefixes(partial.sidebarHiddenTablePrefixes);
|
||||
if (partial.sidebarObjectInfoMode !== undefined) editorSettings.value.sidebarObjectInfoMode = normalizeSidebarObjectInfoMode(partial.sidebarObjectInfoMode);
|
||||
|
||||
Reference in new issue
Block a user