mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-19 16:16:45 +00:00
feat(editor): support excluding completion items
This commit is contained in:
@@ -225,6 +225,7 @@ import LightTooltip from "@/components/ui/LightTooltip.vue";
|
||||
import { formatShortcutDisplay } from "@/lib/editor/shortcutDisplay";
|
||||
import { COLUMN_NAME_COPY_SEPARATOR_LABELS, COLUMN_NAME_COPY_SEPARATOR_OPTIONS, isColumnNameCopySeparator, type ColumnNameCopySeparator } from "@/lib/dataGrid/dataGridColumnNameCopy";
|
||||
import { normalizeSidebarHiddenTablePrefixes } from "@/lib/sidebar/sidebarTableNameDisplay";
|
||||
import { normalizeExcludedCompletionItems } from "@/lib/editor/excludedCompletions";
|
||||
import { normalizeRedisKeyTemplates } from "@/lib/redis/redisKeyTemplates";
|
||||
import { REDIS_DATABASE_DISPLAY_LIMIT_MIN, REDIS_DATABASE_DISPLAY_LIMIT_MAX, REDIS_DATABASE_DISPLAY_LIMIT_OPTIONS } from "@/lib/redis/redisDatabaseAlias";
|
||||
import { currentStatementFrameRangeTo } from "@/lib/sql/currentStatementFrame";
|
||||
@@ -724,6 +725,7 @@ const showSqlServerSpaceConfirmsCompletion = computed(() => hasSqlServerConnecti
|
||||
const editSortCompletionColumnsAlphabetically = ref(settingsStore.editorSettings.sortCompletionColumnsAlphabetically);
|
||||
const editSelectFirstCompletionOnOpen = ref(settingsStore.editorSettings.selectFirstCompletionOnOpen);
|
||||
const editCompletionTriggerMode = ref<SqlCompletionTriggerMode>(settingsStore.editorSettings.completionTriggerMode);
|
||||
const editExcludedCompletionItems = ref(settingsStore.editorSettings.excludedCompletionItems.join("\n"));
|
||||
const completionTriggerModeDescription = computed(() => {
|
||||
const key = {
|
||||
manual: "settings.completionTriggerModeManualDescription",
|
||||
@@ -1121,6 +1123,7 @@ function currentEditorSettingsDraft(): EditorSettingsDraft {
|
||||
sortCompletionColumnsAlphabetically: editSortCompletionColumnsAlphabetically.value,
|
||||
selectFirstCompletionOnOpen: editSelectFirstCompletionOnOpen.value,
|
||||
completionTriggerMode: editCompletionTriggerMode.value,
|
||||
excludedCompletionItems: normalizeExcludedCompletionItems(editExcludedCompletionItems.value),
|
||||
wordWrap: editWordWrap.value,
|
||||
showWhitespace: editShowWhitespace.value,
|
||||
ddlOpenMode: editDdlOpenMode.value,
|
||||
@@ -1807,6 +1810,7 @@ function syncEditorSettingsDraftFromStore() {
|
||||
editSortCompletionColumnsAlphabetically.value = settingsStore.editorSettings.sortCompletionColumnsAlphabetically;
|
||||
editSelectFirstCompletionOnOpen.value = settingsStore.editorSettings.selectFirstCompletionOnOpen;
|
||||
editCompletionTriggerMode.value = settingsStore.editorSettings.completionTriggerMode;
|
||||
editExcludedCompletionItems.value = settingsStore.editorSettings.excludedCompletionItems.join("\n");
|
||||
editWordWrap.value = settingsStore.editorSettings.wordWrap;
|
||||
editShowWhitespace.value = settingsStore.editorSettings.showWhitespace;
|
||||
editDdlOpenMode.value = settingsStore.editorSettings.ddlOpenMode;
|
||||
@@ -2096,6 +2100,7 @@ const editorSettingsDraftRefs: EditorSettingsDraftRefMap = {
|
||||
continueOnErrorOnBatch: editContinueOnErrorOnBatch,
|
||||
clickTableNavigationTarget: editClickTableNavigationTarget,
|
||||
completionTriggerMode: editCompletionTriggerMode,
|
||||
excludedCompletionItems: editExcludedCompletionItems,
|
||||
defaultTransactionMode: editDefaultTransactionMode,
|
||||
keepExplicitTransactionInAutoCommit: editKeepExplicitTransactionInAutoCommit,
|
||||
tableColumnTemplateFields: editTableColumnTemplateRows,
|
||||
@@ -2111,6 +2116,7 @@ function applyEditorSettingsKeysToRefs(draft: EditorSettingsDraft, keys: readonl
|
||||
shortcuts: (value) => normalizeShortcutSettings(value as Parameters<typeof normalizeShortcutSettings>[0]),
|
||||
sqlFormatter: (value) => normalizeSqlFormatterSettings(value as SqlFormatterSettings),
|
||||
sidebarHiddenTablePrefixes: (value) => (value as string[]).join("\n"),
|
||||
excludedCompletionItems: (value) => (value as string[]).join("\n"),
|
||||
redisKeyTemplates: (value) => normalizeRedisKeyTemplates(value as string[]).join("\n"),
|
||||
toolbarItems: (value) => ({ ...(value as EditorSettings["toolbarItems"]) }),
|
||||
snippets: (value) => (value as SqlSnippet[]).map(editableSnippet),
|
||||
@@ -2530,6 +2536,7 @@ function resetDefaultsForTab(tab: SettingsCategory) {
|
||||
editSortCompletionColumnsAlphabetically.value = DEFAULT_EDITOR_SETTINGS.sortCompletionColumnsAlphabetically;
|
||||
editSelectFirstCompletionOnOpen.value = DEFAULT_EDITOR_SETTINGS.selectFirstCompletionOnOpen;
|
||||
editCompletionTriggerMode.value = DEFAULT_EDITOR_SETTINGS.completionTriggerMode;
|
||||
editExcludedCompletionItems.value = DEFAULT_EDITOR_SETTINGS.excludedCompletionItems.join("\n");
|
||||
editWordWrap.value = DEFAULT_EDITOR_SETTINGS.wordWrap;
|
||||
editShowWhitespace.value = DEFAULT_EDITOR_SETTINGS.showWhitespace;
|
||||
editDdlOpenMode.value = DEFAULT_EDITOR_SETTINGS.ddlOpenMode;
|
||||
@@ -6972,6 +6979,19 @@ onUnmounted(() => {
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2 md:col-span-2" data-editor-excluded-completion-items>
|
||||
<Label for="editor-excluded-completion-items">{{ t("settings.excludedCompletionItems") }}</Label>
|
||||
<textarea
|
||||
id="editor-excluded-completion-items"
|
||||
v-model="editExcludedCompletionItems"
|
||||
class="min-h-20 w-full rounded-md border border-input bg-background px-3 py-2 font-mono text-sm outline-none transition-colors placeholder:text-muted-foreground focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring"
|
||||
:placeholder="t('settings.excludedCompletionItemsPlaceholder')"
|
||||
/>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
{{ t("settings.excludedCompletionItemsDescription") }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="settings-item flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2">
|
||||
<div class="space-y-1">
|
||||
<Label for="editor-select-first-completion-on-open">{{ t("settings.selectFirstCompletionOnOpen") }}</Label>
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
extractCteDefinitions,
|
||||
} from "@/lib/sql/sqlCompletion";
|
||||
import { originForSqlCompletionProvider, originForTypedSqlCompletionStart, shouldAllowSqlCompletionTrigger, type SqlCompletionTriggerFacts, type SqlCompletionTriggerOrigin } from "@/lib/sql/sqlCompletionTriggerPolicy";
|
||||
import { excludedCompletionLabelSet, filterExcludedCompletions } from "@/lib/editor/excludedCompletions";
|
||||
import { driverProfileHasCompletionCandidates } from "@/lib/database/driverProfileExtensions";
|
||||
import { buildElasticsearchCompletionItemsFromContext, elasticsearchCompletionNeedsFields, getElasticsearchCompletionContext, getElasticsearchCompletionResultValidFor, shouldAutoOpenElasticsearchCompletion, type ElasticsearchCompletionField } from "@/lib/elasticsearch/elasticsearchCompletion";
|
||||
import {
|
||||
@@ -276,7 +277,8 @@ export function useQueryEditorCompletion(options: QueryEditorCompletionOptions)
|
||||
return true;
|
||||
}
|
||||
|
||||
function buildCompletionResult(items: Array<QueryCompletionItem | BatchColumnSelectionActionItem>, from: number, validFor?: RegExp, prefix?: string) {
|
||||
function buildCompletionResult(sourceItems: Array<QueryCompletionItem | BatchColumnSelectionActionItem>, from: number, validFor?: RegExp, prefix?: string) {
|
||||
const items = filterExcludedCompletions(sourceItems, excludedCompletionLabelSet(settingsStore.editorSettings.excludedCompletionItems));
|
||||
if (items.length === 0) return null;
|
||||
const bypassFilter = !!prefix && shouldBypassCompletionFilter(prefix, items);
|
||||
const resultItems = bypassFilter && prefix ? completionItemsForBypassedFilter(prefix, items) : items;
|
||||
|
||||
@@ -7797,6 +7797,9 @@ export default withEnglishFallback({
|
||||
selectFirstCompletionOnOpen: "İlk tamamlamanı avtomatik seç",
|
||||
selectFirstCompletionOnOpenDescription: "Aktiv olduqda siyahı açılarkən ilk namizəd seçilir və Enter və ya Tab onu qəbul edir. Deaktiv olduqda Enter yeni sətir daxil edir, Tab isə ilk namizədi qəbul edir.",
|
||||
completionTriggerMode: "SQL tamamlamasının işə düşmə rejimi",
|
||||
excludedCompletionItems: "İstisna edilən tamamlama elementləri",
|
||||
excludedCompletionItemsPlaceholder: "WIDTH_BUCKET\nGET_LOCK",
|
||||
excludedCompletionItemsDescription: "Adı siyahıdakı giriylərdən biri ilə uyğun gələn tamamlama təklifləri (hər sətirdə bir, böyük/kiçik hərf fərqi olmadan) SQL redaktorunda göstərilmir.",
|
||||
completionTriggerModeDescription: "SQL tamamlama namizədlərinin necə göstəriləcəyini seçin.",
|
||||
completionTriggerModeManual: "Yalnız əl ilə",
|
||||
completionTriggerModeManualDescription: "Namizədləri yalnız {shortcut} tamamlama qısayolu istifadə edildikdə göstər.",
|
||||
|
||||
@@ -8869,6 +8869,9 @@ export default {
|
||||
selectFirstCompletionOnOpen: "Automatically select the first completion",
|
||||
selectFirstCompletionOnOpenDescription: "When enabled, the first candidate is selected when the list opens, and Enter or Tab accepts it. When disabled, Enter inserts a new line and Tab accepts the first candidate.",
|
||||
completionTriggerMode: "SQL completion trigger mode",
|
||||
excludedCompletionItems: "Excluded completion items",
|
||||
excludedCompletionItemsPlaceholder: "WIDTH_BUCKET\nGET_LOCK",
|
||||
excludedCompletionItemsDescription: "Completion suggestions whose name matches any entry (one per line, case-insensitive) are hidden from the SQL editor.",
|
||||
completionTriggerModeDescription: "Choose how SQL completion candidates are shown.",
|
||||
completionTriggerModeManual: "Manual only",
|
||||
completionTriggerModeManualDescription: "Show candidates only when using the completion shortcut {shortcut}.",
|
||||
|
||||
@@ -8119,6 +8119,9 @@ export default withEnglishFallback({
|
||||
selectFirstCompletionOnOpen: "Seleccionar automáticamente la primera sugerencia",
|
||||
selectFirstCompletionOnOpenDescription: "Al activarlo, se selecciona la primera sugerencia al abrir la lista y se acepta con Enter o Tab. Al desactivarlo, Enter inserta una nueva línea y Tab acepta la primera sugerencia.",
|
||||
completionTriggerMode: "Modo de activación del autocompletado SQL",
|
||||
excludedCompletionItems: "Elementos de autocompletado excluidos",
|
||||
excludedCompletionItemsPlaceholder: "WIDTH_BUCKET\nGET_LOCK",
|
||||
excludedCompletionItemsDescription: "Las sugerencias cuyo nombre coincida con alguna entrada (una por línea, sin distinguir mayúsculas) no aparecen en el autocompletado del editor SQL.",
|
||||
completionTriggerModeDescription: "Elige cómo se muestran las sugerencias de autocompletado SQL.",
|
||||
completionTriggerModeManual: "Solo manual",
|
||||
completionTriggerModeManualDescription: "Muestra sugerencias solo al usar el atajo de autocompletado {shortcut}.",
|
||||
|
||||
@@ -8585,6 +8585,9 @@ export default withEnglishFallback({
|
||||
selectFirstCompletionOnOpen: "Pilih kandidat pertama secara otomatis",
|
||||
selectFirstCompletionOnOpenDescription: "Jika diaktifkan, kandidat pertama dipilih saat daftar terbuka, dan Enter atau Tab menerimanya. Jika dinonaktifkan, Enter menyisipkan baris baru dan Tab menerima kandidat pertama.",
|
||||
completionTriggerMode: "Mode pemicu pelengkapan SQL",
|
||||
excludedCompletionItems: "Item lengkapi otomatis yang dikecualikan",
|
||||
excludedCompletionItemsPlaceholder: "WIDTH_BUCKET\nGET_LOCK",
|
||||
excludedCompletionItemsDescription: "Saran lengkapi otomatis yang namanya cocok dengan salah satu entri (satu per baris, tanpa membedakan huruf besar/kecil) tidak ditampilkan di editor SQL.",
|
||||
completionTriggerModeDescription: "Pilih bagaimana kandidat pelengkapan SQL ditampilkan.",
|
||||
completionTriggerModeManual: "Hanya manual",
|
||||
completionTriggerModeManualDescription: "Tampilkan kandidat hanya saat menggunakan pintasan pelengkapan {shortcut}.",
|
||||
|
||||
@@ -7882,6 +7882,9 @@ export default withEnglishFallback({
|
||||
selectFirstCompletionOnOpen: "Seleziona automaticamente il primo suggerimento",
|
||||
selectFirstCompletionOnOpenDescription: "Se attivo, il primo suggerimento viene selezionato all'apertura dell'elenco e accettato con Invio o Tab. Se disattivo, Invio inserisce una nuova riga e Tab accetta il primo suggerimento.",
|
||||
completionTriggerMode: "Modalità di attivazione del completamento SQL",
|
||||
excludedCompletionItems: "Voci di completamento escluse",
|
||||
excludedCompletionItemsPlaceholder: "WIDTH_BUCKET\nGET_LOCK",
|
||||
excludedCompletionItemsDescription: "I suggerimenti il cui nome corrisponde a una voce (una per riga, senza distinzione maiuscole/minuscole) non vengono mostrati nel completamento dell'editor SQL.",
|
||||
completionTriggerModeDescription: "Scegli come mostrare i suggerimenti di completamento SQL.",
|
||||
completionTriggerModeManual: "Solo manuale",
|
||||
completionTriggerModeManualDescription: "Mostra i suggerimenti solo usando la scorciatoia di completamento {shortcut}.",
|
||||
|
||||
@@ -8604,6 +8604,9 @@ export default withEnglishFallback({
|
||||
selectFirstCompletionOnOpen: "最初の補完候補を自動選択",
|
||||
selectFirstCompletionOnOpenDescription: "有効にすると、候補一覧を開いた時に先頭項目を選択し、Enter または Tab で確定します。無効にすると、Enter は改行し、Tab で先頭候補を確定します。",
|
||||
completionTriggerMode: "SQL補完トリガーモード",
|
||||
excludedCompletionItems: "除外する補完項目",
|
||||
excludedCompletionItemsPlaceholder: "WIDTH_BUCKET\nGET_LOCK",
|
||||
excludedCompletionItemsDescription: "名前がリストのどれかと一致する補完候補(1行に1つ、大文字小文字を区別しない)はSQLエディターの補完に表示されません。",
|
||||
completionTriggerModeDescription: "SQL補完候補の表示方法を選択します。",
|
||||
completionTriggerModeManual: "手動のみ",
|
||||
completionTriggerModeManualDescription: "補完ショートカット {shortcut} を使用したときだけ候補を表示します。",
|
||||
|
||||
@@ -7745,6 +7745,9 @@ export default withEnglishFallback({
|
||||
selectFirstCompletionOnOpen: "첫 번째 자동 완성 후보 자동 선택",
|
||||
selectFirstCompletionOnOpenDescription: "활성화하면 후보 목록이 열릴 때 첫 항목을 선택하고 Enter 또는 Tab으로 적용합니다. 비활성화하면 Enter는 줄을 바꾸고 Tab은 첫 후보를 적용합니다.",
|
||||
completionTriggerMode: "SQL 자동 완성 트리거 모드",
|
||||
excludedCompletionItems: "제외할 자동 완성 항목",
|
||||
excludedCompletionItemsPlaceholder: "WIDTH_BUCKET\nGET_LOCK",
|
||||
excludedCompletionItemsDescription: "이름이 목록의 항목과 일치하는 자동 완성(한 줄에 하나, 대소문자 구분 없음)은 SQL 편집기에 표시되지 않습니다.",
|
||||
completionTriggerModeDescription: "SQL 자동 완성 후보 표시 방식을 선택합니다.",
|
||||
completionTriggerModeManual: "수동만",
|
||||
completionTriggerModeManualDescription: "자동 완성 단축키 {shortcut}을 사용할 때만 후보를 표시합니다.",
|
||||
|
||||
@@ -7880,6 +7880,9 @@ export default withEnglishFallback({
|
||||
selectFirstCompletionOnOpen: "Selecionar automaticamente a primeira sugestão",
|
||||
selectFirstCompletionOnOpenDescription: "Quando ativado, a primeira sugestão é selecionada ao abrir a lista e aceita com Enter ou Tab. Quando desativado, Enter insere uma nova linha e Tab aceita a primeira sugestão.",
|
||||
completionTriggerMode: "Modo de acionamento do autocompletar SQL",
|
||||
excludedCompletionItems: "Itens de autocompletar excluídos",
|
||||
excludedCompletionItemsPlaceholder: "WIDTH_BUCKET\nGET_LOCK",
|
||||
excludedCompletionItemsDescription: "Sugestões cujo nome corresponder a alguma entrada (uma por linha, sem diferenciar maiúsculas) não aparecem no autocompletar do editor SQL.",
|
||||
completionTriggerModeDescription: "Escolha como as sugestões de autocompletar SQL são exibidas.",
|
||||
completionTriggerModeManual: "Apenas manual",
|
||||
completionTriggerModeManualDescription: "Mostra sugestões apenas ao usar o atalho de autocompletar {shortcut}.",
|
||||
|
||||
@@ -9162,6 +9162,9 @@ export default withEnglishFallback({
|
||||
selectFirstCompletionOnOpen: "Автоматически выбирать первое автодополнение",
|
||||
selectFirstCompletionOnOpenDescription: "Если включено, при открытии списка выбирается первый кандидат, и Enter или Tab его принимает. Если отключено, Enter вставляет новую строку, а Tab принимает первого кандидата.",
|
||||
completionTriggerMode: "Режим вызова автодополнения SQL",
|
||||
excludedCompletionItems: "Исключённые подсказки",
|
||||
excludedCompletionItemsPlaceholder: "WIDTH_BUCKET\nGET_LOCK",
|
||||
excludedCompletionItemsDescription: "Подсказки, имя которых совпадает с элементом списка (по одному в строке, без учёта регистра), не отображаются в редакторе SQL.",
|
||||
completionTriggerModeDescription: "Выберите, как показывать кандидатов автодополнения SQL.",
|
||||
completionTriggerModeManual: "Только вручную",
|
||||
completionTriggerModeManualDescription: "Показывать кандидатов только при использовании сочетания автодополнения {shortcut}.",
|
||||
|
||||
@@ -7696,6 +7696,9 @@ export default withEnglishFallback({
|
||||
selectFirstCompletionOnOpen: "İlk tamamlamayı otomatik seç",
|
||||
selectFirstCompletionOnOpenDescription: "Etkinleştirildiğinde liste açıldığında ilk aday seçilir ve Enter ya da Tab bunu kabul eder. Kapatıldığında Enter yeni satır ekler, Tab ilk adayı kabul eder.",
|
||||
completionTriggerMode: "SQL tamamlama tetikleme modu",
|
||||
excludedCompletionItems: "Hariç tutulan tamamlama ögeleri",
|
||||
excludedCompletionItemsPlaceholder: "WIDTH_BUCKET\nGET_LOCK",
|
||||
excludedCompletionItemsDescription: "Adı listedeki bir giriyle eşleşen tamamlama önerileri (satır başına bir, büyük/küçük harf duyarsız) SQL düzenleyicide gösterilmez.",
|
||||
completionTriggerModeDescription: "SQL tamamlama adaylarının nasıl gösterileceğini seçin.",
|
||||
completionTriggerModeManual: "Yalnızca elle",
|
||||
completionTriggerModeManualDescription: "Adayları yalnızca {shortcut} tamamlama kısayolu kullanıldığında göster.",
|
||||
|
||||
@@ -8851,6 +8851,9 @@ export default withEnglishFallback({
|
||||
selectFirstCompletionOnOpen: "自动选中首个补全候选",
|
||||
selectFirstCompletionOnOpenDescription: "开启后,候选列表弹出时会选中第一项,可用 Enter 或 Tab 接受;关闭后,Enter 换行,Tab 接受第一项。",
|
||||
completionTriggerMode: "SQL 补全触发方式",
|
||||
excludedCompletionItems: "排除的自动补全项",
|
||||
excludedCompletionItemsPlaceholder: "WIDTH_BUCKET\nGET_LOCK",
|
||||
excludedCompletionItemsDescription: "名称与列表匹配的补全项(每行一个,不区分大小写)不再出现在 SQL 编辑器的自动补全里。",
|
||||
completionTriggerModeDescription: "选择 SQL 补全候选的显示方式。",
|
||||
completionTriggerModeManual: "仅手动提示",
|
||||
completionTriggerModeManualDescription: "仅使用补全快捷键 {shortcut} 时显示候选。",
|
||||
|
||||
@@ -7225,6 +7225,9 @@ export default withEnglishFallback({
|
||||
selectFirstCompletionOnOpen: "自動選取第一個補全候選",
|
||||
selectFirstCompletionOnOpenDescription: "開啟後,候選清單彈出時會選取第一項,可用 Enter 或 Tab 接受;關閉後,Enter 換行,Tab 接受第一項。",
|
||||
completionTriggerMode: "SQL 補全觸發方式",
|
||||
excludedCompletionItems: "排除的自動補全項",
|
||||
excludedCompletionItemsPlaceholder: "WIDTH_BUCKET\nGET_LOCK",
|
||||
excludedCompletionItemsDescription: "名稱與清單相符的補全項(每行一個,不分大小寫)不會再出現在 SQL 編輯器的自動補全裡。",
|
||||
completionTriggerModeDescription: "選擇 SQL 補全候選的顯示方式。",
|
||||
completionTriggerModeManual: "僅手動提示",
|
||||
completionTriggerModeManualDescription: "僅使用補全快捷鍵 {shortcut} 時顯示候選。",
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { excludedCompletionLabelSet, filterExcludedCompletions, normalizeExcludedCompletionItems } from "@/lib/editor/excludedCompletions";
|
||||
|
||||
describe("excluded completion items", () => {
|
||||
it("normalizes entries from arrays or line/comma separated text", () => {
|
||||
expect(normalizeExcludedCompletionItems([" WIDTH_BUCKET ", "", "get_lock", "GET_LOCK", 42, null])).toEqual(["WIDTH_BUCKET", "get_lock"]);
|
||||
expect(normalizeExcludedCompletionItems("WIDTH_BUCKET, get_lock\nWIDTH_BUCKET")).toEqual(["WIDTH_BUCKET", "get_lock"]);
|
||||
expect(normalizeExcludedCompletionItems(undefined)).toEqual([]);
|
||||
expect(normalizeExcludedCompletionItems({})).toEqual([]);
|
||||
});
|
||||
|
||||
it("filters excluded labels case-insensitively but keeps batch selection actions", () => {
|
||||
const excluded = excludedCompletionLabelSet(["width_bucket"]);
|
||||
const items = [
|
||||
{ label: "WIDTH_BUCKET", type: "function" as const },
|
||||
{ label: "when", type: "keyword" as const },
|
||||
{ label: "users", type: "table" as const, batchColumnSelectionAction: true },
|
||||
];
|
||||
expect(filterExcludedCompletions(items, excluded).map((item) => item.label)).toEqual(["when", "users"]);
|
||||
expect(filterExcludedCompletions(items, new Set())).toEqual(items);
|
||||
});
|
||||
});
|
||||
@@ -4,6 +4,7 @@ import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { acceptSelectedCompletionWithRetry, acceptSelectedOrFirstCompletion } from "@/lib/editor/queryEditorCompletionAcceptance";
|
||||
import { createQueryEditorEscapeHandler } from "@/lib/editor/queryEditorEscape";
|
||||
import { DEFAULT_SHORTCUT_SETTINGS, normalizeShortcutSettings, shortcutToCodeMirrorKey } from "@/lib/editor/shortcutRegistry";
|
||||
import { excludedCompletionLabelSet, filterExcludedCompletions } from "@/lib/editor/excludedCompletions";
|
||||
|
||||
const queryEditorSource = readFileSync(new URL("../../../components/editor/QueryEditor.vue", import.meta.url), "utf8");
|
||||
|
||||
@@ -285,7 +286,9 @@ describe("QueryEditor completion Tab keymap", () => {
|
||||
const javascript = ts.transpileModule(extractFunction("buildCompletionResult"), {
|
||||
compilerOptions: { module: ts.ModuleKind.None, target: ts.ScriptTarget.ES2022 },
|
||||
}).outputText;
|
||||
const buildCompletionResult = new Function("completionOptionForItem", `${javascript}\nreturn buildCompletionResult;`)(() => ({ label: "created_at" })) as (items: Array<{ label: string }>, from: number) => { filter?: boolean; options: Array<{ label: string }>; from: number } | null;
|
||||
const buildCompletionResult = new Function("completionOptionForItem", "filterExcludedCompletions", "excludedCompletionLabelSet", "settingsStore", `${javascript}\nreturn buildCompletionResult;`)(() => ({ label: "created_at" }), filterExcludedCompletions, excludedCompletionLabelSet, {
|
||||
editorSettings: { excludedCompletionItems: [] },
|
||||
}) as (items: Array<{ label: string }>, from: number) => { filter?: boolean; options: Array<{ label: string }>; from: number } | null;
|
||||
|
||||
const result = buildCompletionResult([{ label: "created_at" }], 0);
|
||||
|
||||
@@ -298,9 +301,12 @@ describe("QueryEditor completion Tab keymap", () => {
|
||||
const javascript = ts.transpileModule(source, {
|
||||
compilerOptions: { module: ts.ModuleKind.None, target: ts.ScriptTarget.ES2022 },
|
||||
}).outputText;
|
||||
const buildCompletionResult = new Function("completionOptionForItem", "completionMatchRanges", `${javascript}\nreturn buildCompletionResult;`)(
|
||||
const buildCompletionResult = new Function("completionOptionForItem", "completionMatchRanges", "filterExcludedCompletions", "excludedCompletionLabelSet", "settingsStore", `${javascript}\nreturn buildCompletionResult;`)(
|
||||
(item: { label: string }) => item,
|
||||
() => [],
|
||||
filterExcludedCompletions,
|
||||
excludedCompletionLabelSet,
|
||||
{ editorSettings: { excludedCompletionItems: [] } },
|
||||
) as (items: Array<{ label: string }>, from: number, validFor?: RegExp, prefix?: string) => { filter?: boolean; options: Array<{ label: string }> } | null;
|
||||
|
||||
const result = buildCompletionResult([{ label: "amount" }, { label: "memo" }, { label: "明细" }], 0, undefined, "m");
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/** Normalize the user's excluded-completion list: trim, drop blanks, dedupe case-insensitively. */
|
||||
export function normalizeExcludedCompletionItems(value: unknown): string[] {
|
||||
const source = Array.isArray(value) ? value : typeof value === "string" ? value.split(/\r?\n|,/) : [];
|
||||
const seen = new Set<string>();
|
||||
const result: string[] = [];
|
||||
for (const item of source) {
|
||||
if (typeof item !== "string") continue;
|
||||
const trimmed = item.trim();
|
||||
if (!trimmed) continue;
|
||||
const key = trimmed.toLowerCase();
|
||||
if (seen.has(key)) continue;
|
||||
seen.add(key);
|
||||
result.push(trimmed);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function excludedCompletionLabelSet(items: readonly string[] | undefined): Set<string> {
|
||||
return new Set((items ?? []).map((item) => item.toLowerCase()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop completion items whose label matches an excluded entry
|
||||
* (case-insensitive). Batch column-selection action markers are never
|
||||
* filtered: they are interactive controls, not suggestions.
|
||||
*/
|
||||
export function filterExcludedCompletions<T extends { label: string; batchColumnSelectionAction?: boolean }>(items: readonly T[], excluded: ReadonlySet<string>): T[] {
|
||||
if (excluded.size === 0) return items as T[];
|
||||
return items.filter((item) => item.batchColumnSelectionAction === true || !excluded.has(item.label.toLowerCase()));
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { normalizeResultPageSize } from "@/lib/dataGrid/paginationPageSize";
|
||||
import { normalizeQueryResultMaxRows } from "@/lib/dataGrid/queryResultRowLimit";
|
||||
import { normalizeExternalSqlEditorMaxMb } from "@/lib/sql/sqlFileOpen";
|
||||
import { normalizeCompletionTriggerMode } from "@/lib/sql/sqlCompletionTriggerPolicy";
|
||||
import { normalizeExcludedCompletionItems } from "@/lib/editor/excludedCompletions";
|
||||
import { normalizeSqlTableCompletionSchemaQualification } from "@/lib/sql/sqlCompletionSchemaQualification";
|
||||
import { normalizeTableHoverLookupMode } from "@/lib/editor/hoverTableLookup";
|
||||
import { normalizeRedisKeyTemplates } from "@/lib/redis/redisKeyTemplates";
|
||||
@@ -161,6 +162,7 @@ export const EDITOR_SETTINGS_DRAFT_KEYS = [
|
||||
"continueOnErrorOnBatch",
|
||||
"clickTableNavigationTarget",
|
||||
"completionTriggerMode",
|
||||
"excludedCompletionItems",
|
||||
"defaultTransactionMode",
|
||||
"keepExplicitTransactionInAutoCommit",
|
||||
] as const satisfies readonly (keyof EditorSettings)[];
|
||||
@@ -187,6 +189,7 @@ function normalizedDraftValue(key: EditorSettingsDraftKey, value: unknown): unkn
|
||||
if (key === "queryResultMaxRows") return normalizeQueryResultMaxRowsDraft(value);
|
||||
if (key === "externalSqlEditorMaxMb") return normalizeExternalSqlEditorMaxMb(value);
|
||||
if (key === "completionTriggerMode") return normalizeCompletionTriggerMode(value);
|
||||
if (key === "excludedCompletionItems") return normalizeExcludedCompletionItems(value);
|
||||
if (key === "tableCompletionSchemaQualification") return normalizeSqlTableCompletionSchemaQualification(value);
|
||||
if (key === "tableHoverLookupMode") return normalizeTableHoverLookupMode(value);
|
||||
if (key === "redisKeyTemplates") return normalizeRedisKeyTemplates(value);
|
||||
|
||||
@@ -91,6 +91,7 @@ const SETTINGS_TRANSFER_CATEGORY_KEYS: Record<SettingsTransferCategoryId, readon
|
||||
"sortCompletionColumnsAlphabetically",
|
||||
"selectFirstCompletionOnOpen",
|
||||
"completionTriggerMode",
|
||||
"excludedCompletionItems",
|
||||
"wordWrap",
|
||||
"showWhitespace",
|
||||
"vimModeEnabled",
|
||||
|
||||
@@ -37,6 +37,7 @@ import { DEFAULT_TABLE_COLUMN_TEMPLATE_FIELDS, normalizeTableColumnTemplateField
|
||||
import { type DataTabReuseMode, DEFAULT_DATA_TAB_REUSE_MODE, normalizeDataTabReuseMode } from "@/lib/tabs/dataTabReuseMode";
|
||||
import { normalizeTableHoverLookupMode, type TableHoverLookupMode } from "@/lib/editor/hoverTableLookup";
|
||||
import { normalizeCompletionTriggerMode, type SqlCompletionTriggerMode } from "@/lib/sql/sqlCompletionTriggerPolicy";
|
||||
import { normalizeExcludedCompletionItems } from "@/lib/editor/excludedCompletions";
|
||||
import { DEFAULT_SQL_TABLE_COMPLETION_SCHEMA_QUALIFICATION, normalizeSqlTableCompletionSchemaQualification, type SqlTableCompletionSchemaQualification } from "@/lib/sql/sqlCompletionSchemaQualification";
|
||||
import { DEFAULT_CSV_QUOTE_MODE, normalizeCsvQuoteMode, type CsvQuoteMode } from "@/lib/export/csvQuoteMode";
|
||||
import { DEFAULT_CSV_NULL_MODE, normalizeCsvNullMode, type CsvNullMode } from "@/lib/export/csvNullMode";
|
||||
@@ -1156,6 +1157,8 @@ export interface EditorSettings {
|
||||
tableHoverLookupMode: TableHoverLookupMode;
|
||||
clickTableNavigationTarget: ClickTableNavigationTarget;
|
||||
completionTriggerMode: SqlCompletionTriggerMode;
|
||||
/** Completion labels (case-insensitive) the user never wants suggested. */
|
||||
excludedCompletionItems: string[];
|
||||
defaultTransactionMode: DefaultTransactionMode;
|
||||
/** Auto-commit (`Tx:A`) tabs with a MySQL-family connection: keep a
|
||||
* transaction the user opens explicitly (`BEGIN` / `START TRANSACTION`) open
|
||||
@@ -1475,6 +1478,7 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = {
|
||||
tableHoverLookupMode: "fallback",
|
||||
clickTableNavigationTarget: "data",
|
||||
completionTriggerMode: "positional",
|
||||
excludedCompletionItems: [],
|
||||
defaultTransactionMode: "auto",
|
||||
keepExplicitTransactionInAutoCommit: false,
|
||||
};
|
||||
@@ -2170,6 +2174,7 @@ export function normalizeEditorSettings(settings: Partial<EditorSettings>, exist
|
||||
tableHoverLookupMode: normalizeTableHoverLookupMode(settings.tableHoverLookupMode, DEFAULT_EDITOR_SETTINGS.tableHoverLookupMode),
|
||||
clickTableNavigationTarget: normalizeClickTableNavigationTarget(settings.clickTableNavigationTarget),
|
||||
completionTriggerMode: normalizeCompletionTriggerMode(settings.completionTriggerMode),
|
||||
excludedCompletionItems: normalizeExcludedCompletionItems(settings.excludedCompletionItems),
|
||||
defaultTransactionMode: normalizeDefaultTransactionMode(settings.defaultTransactionMode),
|
||||
keepExplicitTransactionInAutoCommit: settings.keepExplicitTransactionInAutoCommit === true,
|
||||
backgroundImage: normalizeBackgroundImageSettings(settings.backgroundImage),
|
||||
@@ -3016,6 +3021,7 @@ export const useSettingsStore = defineStore("settings", () => {
|
||||
if (partial.tableHoverLookupMode !== undefined) editorSettings.value.tableHoverLookupMode = normalizeTableHoverLookupMode(partial.tableHoverLookupMode, DEFAULT_EDITOR_SETTINGS.tableHoverLookupMode);
|
||||
if (partial.clickTableNavigationTarget !== undefined) editorSettings.value.clickTableNavigationTarget = normalizeClickTableNavigationTarget(partial.clickTableNavigationTarget);
|
||||
if (partial.completionTriggerMode !== undefined) editorSettings.value.completionTriggerMode = normalizeCompletionTriggerMode(partial.completionTriggerMode);
|
||||
if (partial.excludedCompletionItems !== undefined) editorSettings.value.excludedCompletionItems = normalizeExcludedCompletionItems(partial.excludedCompletionItems);
|
||||
if (partial.defaultTransactionMode !== undefined) editorSettings.value.defaultTransactionMode = normalizeDefaultTransactionMode(partial.defaultTransactionMode);
|
||||
if (partial.keepExplicitTransactionInAutoCommit !== undefined) editorSettings.value.keepExplicitTransactionInAutoCommit = partial.keepExplicitTransactionInAutoCommit === true;
|
||||
if (partial.flatteningMultiLineText !== undefined) editorSettings.value.flatteningMultiLineText = partial.flatteningMultiLineText;
|
||||
|
||||
Reference in New Issue
Block a user