feat(editor): add SQL context menu actions

This commit is contained in:
gggaiitx authored and GitHub committed 2026-08-14 12:56:39 +08:00
1 parent 5b40487753
commit b406c0a52c
11 files changed
+64 -4

No files matched your search

@@ -1,6 +1,6 @@
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, onActivated, onDeactivated, watch, shallowRef, computed, nextTick } from "vue";
import { CaseLower, CaseUpper, ClipboardPaste, Code2, Download, FileCode, Pencil, PencilRuler, Play, Copy, List, Scissors, Search, Sparkles, Table2, TextSelect, Trash2 } from "@lucide/vue";
import { AlignLeft, CaseLower, CaseUpper, ClipboardPaste, Code2, Download, FileCode, MessageSquareText, Minimize2, Pencil, PencilRuler, Play, Copy, List, Scissors, Search, Sparkles, Table2, TextSelect, Trash2 } from "@lucide/vue";
import { useI18n } from "vue-i18n";
import type { CompletionContext } from "@codemirror/autocomplete";
import { Transaction, StateEffect } from "@codemirror/state";
@@ -18,7 +18,7 @@ import { executableStatementRangeAtCursor, executableStatementRangeCacheForDoc,
import { currentStatementFrameRangeTo } from "@/lib/sql/currentStatementFrame";
import { expandToSqlStatementWindow } from "@/lib/sql/insertValueHints";
import { insertValueHintColumnNames } from "@/lib/sql/insertValueHintColumns";
import { formatSqlForEditing, compressSqlText, type SqlFormatDialect } from "@/lib/sql/sqlFormatter";
import { canFormatSqlForDatabaseType, formatSqlForEditing, compressSqlText, type SqlFormatDialect } from "@/lib/sql/sqlFormatter";
import { detectAndFormatStructured } from "@/lib/sql/autoFormat";
import { enabledSqlParameterSyntaxes, resolveSqlVariableSyntaxToggles } from "@/lib/sql/sqlVariableSyntax";
import { blankLineDeletionChanges, replaceSelectedEditorText } from "@/lib/editor/queryEditorTextEdits";
@@ -1371,6 +1371,13 @@ async function pasteClipboardSqlFromContextMenu() {
}
}
function toggleCommentFromContextMenu() {
const currentView = view.value;
if (!currentView || props.readOnly) return;
codeMirrorToggleLineComment?.(currentView);
focusEditor();
}
function selectAllSqlFromContextMenu() {
const currentView = view.value;
if (!currentView) return;
@@ -1657,6 +1664,26 @@ const contextMenuItems = computed<ContextMenuItem[]>(() => {
shortcut: shortcuts.expandSelectStar,
},
{ label: "", separator: true },
{
label: t("editor.contextMenu.commentSelection"),
action: toggleCommentFromContextMenu,
disabled: props.readOnly || !canCopySelectedSql.value,
icon: MessageSquareText,
shortcut: shortcuts.toggleLineComment,
},
{
label: t("editor.contextMenu.formatSelectionSql"),
action: () => void formatCurrentSql(),
disabled: props.readOnly || !canCopySelectedSql.value || !canFormatSqlForDatabaseType(props.databaseType),
icon: AlignLeft,
shortcut: shortcuts.formatSql,
},
{
label: t("editor.contextMenu.compressSelectionSql"),
action: compressCurrentSql,
disabled: props.readOnly || !canCopySelectedSql.value,
icon: Minimize2,
},
{
label: t("editor.contextMenu.copySelection"),
action: copySelectedSqlFromContextMenu,
@@ -2888,7 +2915,7 @@ function scheduleSemanticDiagnostics(delay = 500, options: { preserveOutsideRang
async function formatCurrentSql() {
if (props.readOnly) return;
if (props.databaseType === "victoriametrics") return;
if (!canFormatSqlForDatabaseType(props.databaseType)) return;
const currentView = view.value;
if (!currentView) return;
+3
View File
@@ -1264,6 +1264,9 @@ export default {
export: "Export",
exportQueryResultTo: "Export current query result to {format}",
copySelection: "Copy selection",
commentSelection: "Comment selection",
formatSelectionSql: "Format selected SQL",
compressSelectionSql: "Compress selected SQL",
cutSelection: "Cut selection",
pasteFromClipboard: "Paste",
pasteClipboardReadFailed: "Failed to read clipboard: {message}",
+3
View File
@@ -1241,6 +1241,9 @@ export default withEnglishFallback({
export: "Exportar",
exportQueryResultTo: "Exportar el conjunto de resultados de la consulta actual a {format}",
copySelection: "Copiar seleccion",
commentSelection: "Comentar selección",
formatSelectionSql: "Formatear SQL seleccionado",
compressSelectionSql: "Comprimir SQL seleccionado",
sendToAi: "Enviar a IA",
uppercaseSelection: "Convertir a mayusculas",
lowercaseSelection: "Convertir a minusculas",
+3
View File
@@ -1239,6 +1239,9 @@ export default withEnglishFallback({
export: "Esporta",
exportQueryResultTo: "Esporta il set di risultati della query corrente in {format}",
copySelection: "Copia selezione",
commentSelection: "Commenta selezione",
formatSelectionSql: "Formatta SQL selezionato",
compressSelectionSql: "Comprimi SQL selezionato",
sendToAi: "Invia ad AI",
uppercaseSelection: "Converti in maiuscolo",
lowercaseSelection: "Converti in minuscolo",
+3
View File
@@ -1259,6 +1259,9 @@ export default withEnglishFallback({
export: "エクスポート",
exportQueryResultTo: "現在のクエリ結果セットを {format} にエクスポート",
copySelection: "選択範囲をコピー",
commentSelection: "選択範囲をコメント化",
formatSelectionSql: "選択した SQL をフォーマット",
compressSelectionSql: "選択した SQL を圧縮",
sendToAi: "AIに送信",
uppercaseSelection: "大文字に変換",
lowercaseSelection: "小文字に変換",
+3
View File
@@ -1155,6 +1155,9 @@ export default withEnglishFallback({
export: "내보내기",
exportQueryResultTo: "현재 쿼리 결과를 {format}(으)로 내보내기",
copySelection: "선택 영역 복사",
commentSelection: "선택 항목 주석 처리",
formatSelectionSql: "선택한 SQL 서식 지정",
compressSelectionSql: "선택한 SQL 압축",
cutSelection: "선택 영역 잘라내기",
pasteFromClipboard: "붙여넣기",
pasteClipboardReadFailed: "클립보드 읽기 실패: {message}",
+3
View File
@@ -1240,6 +1240,9 @@ export default withEnglishFallback({
export: "Exportar",
exportQueryResultTo: "Exportar conjunto de resultados da consulta atual para {format}",
copySelection: "Copiar seleção",
commentSelection: "Comentar seleção",
formatSelectionSql: "Formatar SQL selecionado",
compressSelectionSql: "Compactar SQL selecionado",
sendToAi: "Enviar para IA",
uppercaseSelection: "Converter para maiúsculas",
lowercaseSelection: "Converter para minúsculas",
+3
View File
@@ -1188,6 +1188,9 @@ export default withEnglishFallback({
export: "导出",
exportQueryResultTo: "导出当前查询结果集到 {format}",
copySelection: "复制选中内容",
commentSelection: "注释选中内容",
formatSelectionSql: "格式化选中SQL",
compressSelectionSql: "压缩选中SQL",
cutSelection: "剪切选中内容",
pasteFromClipboard: "粘贴",
pasteClipboardReadFailed: "读取剪贴板失败:{message}",
+3
View File
@@ -1239,6 +1239,9 @@ export default withEnglishFallback({
export: "匯出",
exportQueryResultTo: "匯出目前查詢結果集到 {format}",
copySelection: "複製選取內容",
commentSelection: "註解選取內容",
formatSelectionSql: "格式化選取的 SQL",
compressSelectionSql: "壓縮選取的 SQL",
sendToAi: "傳送至 AI",
uppercaseSelection: "轉為大寫",
lowercaseSelection: "轉為小寫",
+6 -1
View File
@@ -1,7 +1,12 @@
import { describe, expect, it } from "vitest";
import { formatSqlForDisplay, formatSqlForEditing, formatSqlText, MAX_SQL_FORMAT_CHARS, sqlFormatDialectForDbType, UnsupportedStructuredInputError } from "@/lib/sql/sqlFormatter";
import { canFormatSqlForDatabaseType, formatSqlForDisplay, formatSqlForEditing, formatSqlText, MAX_SQL_FORMAT_CHARS, sqlFormatDialectForDbType, UnsupportedStructuredInputError } from "@/lib/sql/sqlFormatter";
describe("sqlFormatter", () => {
it("disables SQL formatting for VictoriaMetrics queries", () => {
expect(canFormatSqlForDatabaseType("victoriametrics")).toBe(false);
expect(canFormatSqlForDatabaseType("mysql")).toBe(true);
});
it("maps PostgreSQL-compatible database types to the postgres formatter dialect", () => {
for (const dbType of ["postgres", "kwdb", "gaussdb", "opengauss", "questdb", "kingbase", "highgo", "vastbase", "redshift"]) {
expect(sqlFormatDialectForDbType(dbType)).toBe("postgres");
+4
View File
@@ -5,6 +5,10 @@ export type SqlFormatDialect = "mysql" | "postgres" | "sqlite" | "sqlserver" | "
export const MAX_SQL_FORMAT_CHARS = 1_000_000;
export function canFormatSqlForDatabaseType(dbType: string | null | undefined): boolean {
return dbType !== "victoriametrics";
}
/**
* Thrown by {@link formatSqlText} when the input is XML-looking and must never
* be run through the SQL formatter. sql-formatter silently rewrites well-formed