diff --git a/src/components/editor/EditorSettingsDialog.vue b/src/components/editor/EditorSettingsDialog.vue index c1bf6eee90..2a8b07c6b4 100644 --- a/src/components/editor/EditorSettingsDialog.vue +++ b/src/components/editor/EditorSettingsDialog.vue @@ -30,6 +30,7 @@ const editFontFamily = ref(settingsStore.editorSettings.fontFamily); const editFontSize = ref(settingsStore.editorSettings.fontSize); const editTheme = ref(settingsStore.editorSettings.theme); const editExecuteMode = ref(settingsStore.editorSettings.executeMode); +const editWordWrap = ref(settingsStore.editorSettings.wordWrap); // Sync from store when dialog opens watch( @@ -40,6 +41,7 @@ watch( editFontSize.value = settingsStore.editorSettings.fontSize; editTheme.value = settingsStore.editorSettings.theme; editExecuteMode.value = settingsStore.editorSettings.executeMode; + editWordWrap.value = settingsStore.editorSettings.wordWrap; } }, ); @@ -49,7 +51,8 @@ function hasChanges(): boolean { editFontFamily.value !== settingsStore.editorSettings.fontFamily || editFontSize.value !== settingsStore.editorSettings.fontSize || editTheme.value !== settingsStore.editorSettings.theme || - editExecuteMode.value !== settingsStore.editorSettings.executeMode + editExecuteMode.value !== settingsStore.editorSettings.executeMode || + editWordWrap.value !== settingsStore.editorSettings.wordWrap ); } @@ -59,6 +62,7 @@ function applySettings() { fontSize: editFontSize.value, theme: editTheme.value, executeMode: editExecuteMode.value, + wordWrap: editWordWrap.value, }); emit("update:open", false); } @@ -68,6 +72,7 @@ function resetDefaults() { editFontSize.value = DEFAULT_EDITOR_SETTINGS.fontSize; editTheme.value = DEFAULT_EDITOR_SETTINGS.theme; editExecuteMode.value = DEFAULT_EDITOR_SETTINGS.executeMode; + editWordWrap.value = DEFAULT_EDITOR_SETTINGS.wordWrap; } function onExecuteModeChange(v: any) { @@ -340,6 +345,16 @@ watch( +
+
+ +

{{ t("settings.wordWrapDescription") }}

+
+ +
+ + +
diff --git a/src/components/editor/QueryEditor.vue b/src/components/editor/QueryEditor.vue index f6e9b934ef..01ba0d2b63 100644 --- a/src/components/editor/QueryEditor.vue +++ b/src/components/editor/QueryEditor.vue @@ -15,7 +15,7 @@ const props = defineProps<{ modelValue: string; connectionId?: string; database?: string; - dialect?: "mysql" | "postgres"; + dialect?: "mysql" | "postgres" | "sqlserver"; formatDialect?: SqlFormatDialect; formatRequestId?: number; }>(); @@ -40,6 +40,7 @@ const MAX_FONT_SIZE = 24; let editorViewModule: typeof import("@codemirror/view") | null = null; let fontThemeComp: import("@codemirror/state").Compartment | null = null; let codeMirrorTheme: import("@codemirror/state").Compartment | null = null; +let wordWrapComp: import("@codemirror/state").Compartment | null = null; // Completion cache let cachedTables: Array<{ name: string; schema?: string; type?: "table" | "view" }> = []; @@ -206,8 +207,8 @@ onMounted(async () => { const [ { EditorView, keymap }, - { EditorState, Compartment }, - { sql, MySQL, PostgreSQL, SQLDialect }, + { EditorState, Compartment, Prec }, + { sql, MSSQL, MySQL, PostgreSQL, SQLDialect }, { basicSetup }, { autocompletion, startCompletion }, { indentWithTab }, @@ -222,10 +223,11 @@ onMounted(async () => { editorViewModule = { EditorView, keymap } as typeof import("@codemirror/view"); fontThemeComp = new Compartment(); codeMirrorTheme = new Compartment(); + wordWrapComp = new Compartment(); const ss = settingsStore.editorSettings; - const baseDialect = props.dialect === "postgres" ? PostgreSQL : MySQL; + const baseDialect = props.dialect === "postgres" ? PostgreSQL : props.dialect === "sqlserver" ? MSSQL : MySQL; const extraKeywords = "PIVOT UNPIVOT EXCLUDE REPLACE QUALIFY ASOF POSITIONAL ANTI SEMI SAMPLE TABLESAMPLE STRUCT MAP LIST ARRAY LAMBDA UNNEST LATERAL FILTER RECURSIVE SUMMARIZE PRAGMA READ_CSV READ_PARQUET READ_JSON DESCRIBE SHOW COPY EXPORT IMPORT"; const dialect = SQLDialect.define({ @@ -283,8 +285,9 @@ onMounted(async () => { override: [async (context: CompletionContext) => provideSqlCompletions(context.state, context.pos)], }), codeMirrorTheme.of(theme), - keymap.of([indentWithTab]), + Prec.highest(keymap.of([indentWithTab])), runKeymap, + wordWrapComp.of(ss.wordWrap ? EditorView.lineWrapping : []), EditorView.updateListener.of((update) => { if (update.docChanged) { emit("update:modelValue", update.state.doc.toString()); @@ -486,11 +489,12 @@ watch( watch( () => settingsStore.editorSettings, async (ss) => { - if (!view.value || !codeMirrorTheme || !fontThemeComp || !editorViewModule) return; + if (!view.value || !codeMirrorTheme || !fontThemeComp || !wordWrapComp || !editorViewModule) return; const themeExt = await loadEditorTheme(ss.theme); view.value.dispatch({ effects: [ codeMirrorTheme.reconfigure(themeExt), + wordWrapComp.reconfigure(ss.wordWrap ? editorViewModule.EditorView.lineWrapping : []), fontThemeComp.reconfigure( editorFontTheme(editorViewModule.EditorView, ss.fontSize, ss.fontFamily, { fixedHeight: true, diff --git a/src/components/layout/ContentArea.vue b/src/components/layout/ContentArea.vue index 7abe6cdd68..1bcee57967 100644 --- a/src/components/layout/ContentArea.vue +++ b/src/components/layout/ContentArea.vue @@ -71,9 +71,11 @@ const activeSqlFormatDialect = computed(() => { } }); -const editorDialect = computed<"mysql" | "postgres">(() => - props.activeConnection?.db_type === "postgres" ? "postgres" : "mysql", -); +const editorDialect = computed<"mysql" | "postgres" | "sqlserver">(() => { + if (props.activeConnection?.db_type === "postgres") return "postgres"; + if (props.activeConnection?.db_type === "sqlserver") return "sqlserver"; + return "mysql"; +}); const hasNumericData = computed(() => { const r = props.activeTab.result; diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index 60229dd431..b8b9176f0d 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -737,6 +737,8 @@ export default { executeMode: "Execute Mode (Cmd+Enter)", executeModeAll: "Execute all SQL", executeModeCurrent: "Execute statement at cursor", + wordWrap: "Word wrap", + wordWrapDescription: "Wrap long SQL lines within the editor width", preview: "Live Preview", aboutDescription: "An open-source, lightweight database management tool.", community: "Community", diff --git a/src/i18n/locales/zh-CN.ts b/src/i18n/locales/zh-CN.ts index 76da0152a2..17149b2f55 100644 --- a/src/i18n/locales/zh-CN.ts +++ b/src/i18n/locales/zh-CN.ts @@ -725,6 +725,8 @@ export default { executeMode: "执行模式 (Cmd+Enter)", executeModeAll: "执行全部 SQL", executeModeCurrent: "执行光标所在语句", + wordWrap: "自动换行", + wordWrapDescription: "长 SQL 在编辑器宽度内自动折行显示", preview: "实时预览", aboutDescription: "开源、轻量的数据库管理工具。", community: "社区", diff --git a/src/lib/sqlCompletion.ts b/src/lib/sqlCompletion.ts index 8863a507fd..0cdda30c76 100644 --- a/src/lib/sqlCompletion.ts +++ b/src/lib/sqlCompletion.ts @@ -84,6 +84,15 @@ const SQL_KEYWORDS = [ "APPLY", "CROSS APPLY", "OUTER APPLY", + "ISJSON", + "JSON_ARRAY", + "JSON_ARRAYAGG", + "JSON_MODIFY", + "JSON_OBJECT", + "JSON_OBJECTAGG", + "JSON_PATH_EXISTS", + "JSON_QUERY", + "JSON_VALUE", "OPENJSON", "OPENXML", "OPENROWSET", diff --git a/src/stores/settingsStore.ts b/src/stores/settingsStore.ts index f14409feb0..7d375900a4 100644 --- a/src/stores/settingsStore.ts +++ b/src/stores/settingsStore.ts @@ -45,6 +45,7 @@ export interface EditorSettings { fontSize: number; theme: EditorTheme; executeMode: "all" | "current"; + wordWrap: boolean; } export const EDITOR_THEMES: { value: EditorTheme; label: string; dark: boolean }[] = [ @@ -74,6 +75,7 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = { fontSize: 13, theme: "one-dark", executeMode: "all", + wordWrap: false, }; export const STORAGE_KEY = "dbx-editor-settings"; @@ -90,6 +92,7 @@ function loadEditorSettings(): EditorSettings { fontSize: parsed.fontSize ?? DEFAULT_EDITOR_SETTINGS.fontSize, theme: parsed.theme ?? DEFAULT_EDITOR_SETTINGS.theme, executeMode: parsed.executeMode ?? DEFAULT_EDITOR_SETTINGS.executeMode, + wordWrap: parsed.wordWrap ?? DEFAULT_EDITOR_SETTINGS.wordWrap, }; } } catch {