feat(editor): add SQL block comment action

This commit is contained in:
Melody Ruch authored and GitHub committed 2026-08-16 11:29:03 +08:00
1 parent 34a4f5d78a
commit e18222628d
14 files changed
+127 -3

No files matched your search

@@ -120,7 +120,7 @@ import { LARGE_PASTE_HISTORY_USER_EVENT, normalizeQueryEditorPasteText, recovera
import { computePasteCaretResyncTarget } from "@/lib/editor/queryEditorPasteCaretResync";
import { extendQueryEditorSelection, runQueryEditorAltExtendSelection } from "@/lib/editor/queryEditorExtendSelection";
import type { StatementExecutionMarker } from "@/lib/tabs/tabPresentation";
import { isSchemaAware, isSingleDatabase, supportsDatabaseNameCompletion, supportsDatabaseSchemaQualifier, supportsSqlInListPaste } from "@/lib/database/databaseFeatureSupport";
import { isSchemaAware, isSingleDatabase, supportsDatabaseNameCompletion, supportsDatabaseSchemaQualifier, supportsQueryEditorBlockComments, supportsSqlInListPaste } from "@/lib/database/databaseFeatureSupport";
import { metadataSchemaForConnection, sqlSnippetDatabaseTypeForConnection } from "@/lib/database/jdbcDialect";
import { usesLocalOnlyEditorCompletionMetadata, usesOnDemandOnlyEditorColumnMetadata } from "@/lib/metadata/completionMetadataPolicy";
import { loadTableMetadata } from "@/lib/metadata/tableMetadataCache";
@@ -548,6 +548,7 @@ let codeMirrorRedo: typeof import("@codemirror/commands").redo | null = null;
let codeMirrorSelectAll: typeof import("@codemirror/commands").selectAll | null = null;
let codeMirrorInsertNewlineKeepIndent: typeof import("@codemirror/commands").insertNewlineKeepIndent | null = null;
let codeMirrorToggleLineComment: typeof import("@codemirror/commands").toggleLineComment | null = null;
let codeMirrorToggleBlockComment: typeof import("@codemirror/commands").toggleBlockComment | null = null;
let codeMirrorToggleFold: typeof import("@codemirror/language").toggleFold | null = null;
let pendingCompletionTabTimer: ReturnType<typeof setTimeout> | null = null;
let setSqlDiagnosticsEffect: import("@codemirror/state").StateEffectType<SqlSemanticDiagnostic[]> | null = null;
@@ -1401,6 +1402,13 @@ function toggleCommentFromContextMenu() {
focusEditor();
}
function toggleBlockCommentFromContextMenu() {
const currentView = view.value;
if (!currentView || props.readOnly || !supportsQueryEditorBlockComments(props.databaseType)) return;
codeMirrorToggleBlockComment?.(currentView);
focusEditor();
}
function selectAllSqlFromContextMenu() {
const currentView = view.value;
if (!currentView) return;
@@ -1709,6 +1717,13 @@ const contextMenuItems = computed<ContextMenuItem[]>(() => {
icon: MessageSquareText,
shortcut: shortcuts.toggleLineComment,
},
{
label: t("editor.contextMenu.blockCommentSelection"),
action: toggleBlockCommentFromContextMenu,
disabled: props.readOnly || !canCopySelectedSql.value || !supportsQueryEditorBlockComments(props.databaseType),
icon: MessageSquareText,
shortcut: shortcuts.toggleBlockComment,
},
{
label: t("editor.contextMenu.formatSelectionSql"),
action: () => void formatCurrentSql(),
@@ -1880,6 +1895,10 @@ function runKeymapExtension(codeMirrorKeymap: (typeof import("@codemirror/view")
...binding(shortcuts.uppercaseSelection, () => convertSelectedSqlCase("upper")),
...binding(shortcuts.lowercaseSelection, () => convertSelectedSqlCase("lower")),
...binding(shortcuts.toggleLineComment, (view) => codeMirrorToggleLineComment?.(view) ?? false),
...binding(shortcuts.toggleBlockComment, (view) => {
if (!supportsQueryEditorBlockComments(props.databaseType)) return false;
return codeMirrorToggleBlockComment?.(view) ?? false;
}),
...binding(shortcuts.toggleFold, (view) => codeMirrorToggleFold?.(view) ?? false),
...binding(shortcuts.exPasteSqlInCondition, () => {
if (!supportsSqlInListPaste(props.databaseType)) return false;
@@ -4412,7 +4431,7 @@ onMounted(async () => {
{ EditorState, EditorSelection, Compartment, Prec, RangeSet, StateEffect, StateField },
langSql,
{ autocompletion, startCompletion, acceptCompletion, closeBrackets, closeBracketsKeymap, snippetCompletion, completionStatus, completionKeymap, insertCompletionText, nextSnippetField, closeCompletion },
{ copyLineDown, copyLineUp, deleteLine, indentLess, indentMore, insertNewlineKeepIndent, moveLineDown, moveLineUp, redo, selectAll, undo, toggleLineComment, history, defaultKeymap, historyKeymap },
{ copyLineDown, copyLineUp, deleteLine, indentLess, indentMore, insertNewlineKeepIndent, moveLineDown, moveLineUp, redo, selectAll, undo, toggleLineComment, toggleBlockComment, history, defaultKeymap, historyKeymap },
{ bracketMatching, foldGutter, indentOnInput, indentUnit, syntaxHighlighting, defaultHighlightStyle, foldKeymap, toggleFold, ensureSyntaxTree },
{ searchKeymap },
] = await Promise.all([import("@codemirror/view"), import("@codemirror/state"), import("@codemirror/lang-sql"), import("@codemirror/autocomplete"), import("@codemirror/commands"), import("@codemirror/language"), import("@codemirror/search")]);
@@ -4461,6 +4480,7 @@ onMounted(async () => {
codeMirrorSelectAll = selectAll;
codeMirrorInsertNewlineKeepIndent = insertNewlineKeepIndent;
codeMirrorToggleLineComment = toggleLineComment;
codeMirrorToggleBlockComment = toggleBlockComment;
codeMirrorToggleFold = toggleFold;
codeMirrorIndentUnit = indentUnit;
window.addEventListener("keyup", clearTableNavigationHoverOnModifierRelease);
@@ -4856,7 +4876,13 @@ onMounted(async () => {
activeLineHighlighter,
// Vim must be mounted before DBX/default keymaps so normal-mode keys are handled first.
vimModeComp.of(vimModeExtension(initialSettings.vimModeEnabled)),
keymap.of([...defaultKeymap, ...searchKeymapWithoutModD(searchKeymap), ...historyKeymap, ...foldKeymap, ...completionKeymap]),
keymap.of([
...defaultKeymap.filter((item) => item.run !== toggleBlockComment),
...searchKeymapWithoutModD(searchKeymap),
...historyKeymap,
...foldKeymap,
...completionKeymap,
]),
sqlLanguageComp.of(buildSqlLanguageExtension()),
sqlSemanticHighlightComp.of(buildSqlSemanticHighlightExtension()),
tooltips({ parent: tooltipParent }),
+2
View File
@@ -1277,6 +1277,7 @@ export default {
exportQueryResultTo: "Export current query result to {format}",
copySelection: "Copy selection",
commentSelection: "Comment selection",
blockCommentSelection: "Block comment selection",
formatSelectionSql: "Format selected SQL",
compressSelectionSql: "Compress selected SQL",
cutSelection: "Cut selection",
@@ -6047,6 +6048,7 @@ export default {
shortcutFormatSql: "Format SQL",
shortcutExpandSelectStar: "Expand * to columns",
shortcutToggleLineComment: "Toggle line comment",
shortcutToggleBlockComment: "Toggle block comment",
shortcutIndentMore: "Indent more",
shortcutIndentLess: "Indent less",
shortcutInsertLineBelow: "Insert line below",
+2
View File
@@ -1255,6 +1255,7 @@ export default withEnglishFallback({
exportQueryResultTo: "Exportar el conjunto de resultados de la consulta actual a {format}",
copySelection: "Copiar seleccion",
commentSelection: "Comentar selección",
blockCommentSelection: "Comentario de bloque en la selección",
formatSelectionSql: "Formatear SQL seleccionado",
compressSelectionSql: "Comprimir SQL seleccionado",
sendToAi: "Enviar a IA",
@@ -5748,6 +5749,7 @@ export default withEnglishFallback({
shortcutFormatSql: "Formatear SQL",
shortcutExpandSelectStar: "Expandir * a columnas",
shortcutToggleLineComment: "Alternar comentario de línea",
shortcutToggleBlockComment: "Alternar comentario de bloque",
shortcutIndentMore: "Aumentar sangría",
shortcutIndentLess: "Reducir sangría",
shortcutInsertLineBelow: "Insertar una línea debajo",
+2
View File
@@ -1253,6 +1253,7 @@ export default withEnglishFallback({
exportQueryResultTo: "Esporta il set di risultati della query corrente in {format}",
copySelection: "Copia selezione",
commentSelection: "Commenta selezione",
blockCommentSelection: "Commenta selezione a blocco",
formatSelectionSql: "Formatta SQL selezionato",
compressSelectionSql: "Comprimi SQL selezionato",
sendToAi: "Invia ad AI",
@@ -5748,6 +5749,7 @@ export default withEnglishFallback({
shortcutFormatSql: "Formatta SQL",
shortcutExpandSelectStar: "Espandi * in colonne",
shortcutToggleLineComment: "Attiva/disattiva commento di riga",
shortcutToggleBlockComment: "Attiva/disattiva commento a blocco",
shortcutIndentMore: "Aumenta rientro",
shortcutIndentLess: "Riduci rientro",
shortcutInsertLineBelow: "Inserisci una riga sotto",
+2
View File
@@ -1273,6 +1273,7 @@ export default withEnglishFallback({
exportQueryResultTo: "現在のクエリ結果セットを {format} にエクスポート",
copySelection: "選択範囲をコピー",
commentSelection: "選択範囲をコメント化",
blockCommentSelection: "選択範囲をブロックコメント化",
formatSelectionSql: "選択した SQL をフォーマット",
compressSelectionSql: "選択した SQL を圧縮",
sendToAi: "AIに送信",
@@ -5763,6 +5764,7 @@ export default withEnglishFallback({
shortcutFormatSql: "SQLをフォーマット",
shortcutExpandSelectStar: "* を列に展開",
shortcutToggleLineComment: "行コメントを切り替え",
shortcutToggleBlockComment: "ブロックコメントを切り替え",
shortcutIndentMore: "インデントを増やす",
shortcutIndentLess: "インデントを減らす",
shortcutInsertLineBelow: "下に行を挿入",
+2
View File
@@ -1164,6 +1164,7 @@ export default withEnglishFallback({
exportQueryResultTo: "현재 쿼리 결과를 {format}(으)로 내보내기",
copySelection: "선택 영역 복사",
commentSelection: "선택 항목 주석 처리",
blockCommentSelection: "선택 항목 블록 주석 처리",
formatSelectionSql: "선택한 SQL 서식 지정",
compressSelectionSql: "선택한 SQL 압축",
cutSelection: "선택 영역 잘라내기",
@@ -5506,6 +5507,7 @@ export default withEnglishFallback({
shortcutFormatSql: "SQL 서식",
shortcutExpandSelectStar: "*을(를) 열로 확장",
shortcutToggleLineComment: "줄 주석 전환",
shortcutToggleBlockComment: "블록 주석 전환",
shortcutIndentMore: "들여쓰기 늘리기",
shortcutIndentLess: "들여쓰기 줄이기",
shortcutInsertLineBelow: "아래에 줄 삽입",
+2
View File
@@ -1254,6 +1254,7 @@ export default withEnglishFallback({
exportQueryResultTo: "Exportar conjunto de resultados da consulta atual para {format}",
copySelection: "Copiar seleção",
commentSelection: "Comentar seleção",
blockCommentSelection: "Comentário de bloco na seleção",
formatSelectionSql: "Formatar SQL selecionado",
compressSelectionSql: "Compactar SQL selecionado",
sendToAi: "Enviar para IA",
@@ -5750,6 +5751,7 @@ export default withEnglishFallback({
shortcutFormatSql: "Formatar SQL",
shortcutExpandSelectStar: "Expandir * em colunas",
shortcutToggleLineComment: "Alternar comentário de linha",
shortcutToggleBlockComment: "Alternar comentário de bloco",
shortcutIndentMore: "Aumentar recuo",
shortcutIndentLess: "Reduzir recuo",
shortcutInsertLineBelow: "Inserir uma linha abaixo",
+2
View File
@@ -1201,6 +1201,7 @@ export default withEnglishFallback({
exportQueryResultTo: "导出当前查询结果集到 {format}",
copySelection: "复制选中内容",
commentSelection: "注释选中内容",
blockCommentSelection: "块注释选中内容",
formatSelectionSql: "格式化选中SQL",
compressSelectionSql: "压缩选中SQL",
cutSelection: "剪切选中内容",
@@ -6033,6 +6034,7 @@ export default withEnglishFallback({
shortcutFormatSql: "格式化 SQL",
shortcutExpandSelectStar: "将 * 展开为字段",
shortcutToggleLineComment: "切换行注释",
shortcutToggleBlockComment: "切换块注释",
shortcutIndentMore: "增加缩进",
shortcutIndentLess: "减少缩进",
shortcutInsertLineBelow: "在下方新增一行",
+2
View File
@@ -1253,6 +1253,7 @@ export default withEnglishFallback({
exportQueryResultTo: "匯出目前查詢結果集到 {format}",
copySelection: "複製選取內容",
commentSelection: "註解選取內容",
blockCommentSelection: "區塊註解選取內容",
formatSelectionSql: "格式化選取的 SQL",
compressSelectionSql: "壓縮選取的 SQL",
sendToAi: "傳送至 AI",
@@ -5074,6 +5075,7 @@ export default withEnglishFallback({
shortcutFormatSql: "格式化 SQL",
shortcutExpandSelectStar: "將 * 展開為欄位",
shortcutToggleLineComment: "切換行註解",
shortcutToggleBlockComment: "切換區塊註解",
shortcutIndentMore: "增加縮排",
shortcutIndentLess: "減少縮排",
shortcutInsertLineBelow: "在下方新增一行",
@@ -9,6 +9,7 @@ import {
supportsDatabaseNameCompletion,
supportsDatabaseSchemaQualifier,
supportsQueryTargetDatabaseListing,
supportsQueryEditorBlockComments,
supportsSqlInListPaste,
supportsTableImport,
supportsTransaction,
@@ -123,6 +124,21 @@ describe("supportsSqlInListPaste", () => {
});
});
describe("supportsQueryEditorBlockComments", () => {
it("allows block comments in generic and SQL editors", () => {
expect(supportsQueryEditorBlockComments(undefined)).toBe(true);
expect(supportsQueryEditorBlockComments("mysql")).toBe(true);
expect(supportsQueryEditorBlockComments("postgres")).toBe(true);
expect(supportsQueryEditorBlockComments("sqlserver")).toBe(true);
});
it("hides block comments in non-SQL editors", () => {
expect(supportsQueryEditorBlockComments("redis")).toBe(false);
expect(supportsQueryEditorBlockComments("mongodb")).toBe(false);
expect(supportsQueryEditorBlockComments("elasticsearch")).toBe(false);
});
});
describe("supportsTableImport", () => {
it("enables OceanBase Oracle table import", () => {
expect(supportsTableImport("oceanbase-oracle")).toBe(true);
@@ -0,0 +1,52 @@
import { readFileSync } from "node:fs";
import { toggleBlockComment } from "@codemirror/commands";
import { sql } from "@codemirror/lang-sql";
import { EditorSelection, EditorState, type Transaction } from "@codemirror/state";
import { describe, expect, it, vi } from "vitest";
const queryEditorSource = readFileSync(new URL("../../../components/editor/QueryEditor.vue", import.meta.url), "utf8");
function runToggleBlockComment(doc: string, selection: EditorSelection | { anchor: number; head?: number }) {
let state = EditorState.create({ doc, selection, extensions: [sql()] });
const dispatch = vi.fn((transaction: Transaction) => {
state = transaction.state;
});
const handled = toggleBlockComment({
get state() {
return state;
},
dispatch,
} as never);
return { dispatch, handled, state };
}
describe("QueryEditor block comment", () => {
it("wires toggleBlockComment into the right-click context menu, the keymap, and the editor module load", () => {
expect(queryEditorSource).toContain('let codeMirrorToggleBlockComment: typeof import("@codemirror/commands").toggleBlockComment | null = null;');
expect(queryEditorSource).toContain("codeMirrorToggleBlockComment?.(currentView);");
expect(queryEditorSource).toContain('label: t("editor.contextMenu.blockCommentSelection")');
expect(queryEditorSource).toContain("shortcut: shortcuts.toggleBlockComment");
expect(queryEditorSource).toContain("...binding(shortcuts.toggleBlockComment, (view) => {");
expect(queryEditorSource).toContain("!supportsQueryEditorBlockComments(props.databaseType)");
expect(queryEditorSource).toContain("defaultKeymap.filter((item) => item.run !== toggleBlockComment)");
expect(queryEditorSource).toContain("codeMirrorToggleBlockComment = toggleBlockComment;");
});
it("wraps the selected SQL in a block comment", () => {
const doc = "SELECT id FROM users;";
const selection = EditorSelection.range(doc.indexOf("id"), doc.indexOf("id") + "id".length);
const result = runToggleBlockComment(doc, selection);
expect(result.handled).toBe(true);
expect(result.state.doc.toString()).toBe("SELECT /* id */ FROM users;");
});
it("removes an existing block comment around the selection", () => {
const doc = "SELECT /* id */ FROM users;";
const selection = EditorSelection.range(doc.indexOf("/*"), doc.indexOf("*/") + "*/".length);
const result = runToggleBlockComment(doc, selection);
expect(result.state.doc.toString()).toBe("SELECT id FROM users;");
});
});
@@ -5,6 +5,7 @@ describe("shortcutRegistry editor actions", () => {
const formatterEditorActionIds: ShortcutActionId[] = [
"formatSql",
"toggleLineComment",
"toggleBlockComment",
"indentMore",
"indentLess",
"joinLines",
@@ -126,6 +127,7 @@ describe("shortcutRegistry editor actions", () => {
expect(shortcuts.executeSql).toBe("Mod+Shift+Enter");
expect(shortcuts.formatSql).toBe("Shift+Mod+F");
expect(shortcuts.toggleLineComment).toBe("Mod+/");
expect(shortcuts.toggleBlockComment).toBe("Shift+Alt+A");
expect(shortcuts.indentMore).toBe("");
expect(shortcuts.indentLess).toBe("Shift+Tab");
expect(shortcuts.joinLines).toBe("Mod+J");
@@ -131,6 +131,11 @@ export function supportsSqlInListPaste(dbType?: DatabaseType): boolean {
return supportsSqlFileExecution(dbType) && !NON_SQL_IN_LIST_PASTE_TYPES.has(dbType);
}
export function supportsQueryEditorBlockComments(dbType?: DatabaseType): boolean {
if (!dbType) return true;
return supportsSqlFileExecution(dbType);
}
export function supportsSchemaDiagram(dbType?: DatabaseType): boolean {
return supportsDatabaseFeature(dbType, "diagram");
}
@@ -6,6 +6,7 @@ export type ShortcutActionId =
| "formatSql"
| "expandSelectStar"
| "toggleLineComment"
| "toggleBlockComment"
| "saveSql"
| "acceptCompletion"
| "indentMore"
@@ -119,6 +120,12 @@ export const SHORTCUT_DEFINITIONS: ShortcutDefinition[] = [
scope: "editor",
defaultShortcut: "Mod+/",
},
{
id: "toggleBlockComment",
labelKey: "settings.shortcutToggleBlockComment",
scope: "editor",
defaultShortcut: "Shift+Alt+A",
},
{
id: "saveSql",
labelKey: "settings.shortcutSaveSql",