feat(editor): add semantic selection and match-all cursors
This commit is contained in:
1 parent
02bad5d754
commit
ed26fc10f5
29 files changed
+1794
-54
No files matched your search
@@ -4,8 +4,10 @@ import { useI18n } from "vue-i18n";
|
||||
import type { EditorView } from "@codemirror/view";
|
||||
import { EditorSelection } from "@codemirror/state";
|
||||
import { setSearchQuery, openSearchPanel as cmOpenSearchPanel, findNext as cmFindNext, findPrevious as cmFindPrevious, replaceNext as cmReplaceNext, replaceAll as cmReplaceAll } from "@codemirror/search";
|
||||
import { ChevronUp, ChevronDown, ChevronRight, X } from "@lucide/vue";
|
||||
import { collectEditorSearchMatches, createEditorSearchQuery, replaceEditorSearchMatches } from "@/lib/editor/editorSearchQuery";
|
||||
import { ChevronUp, ChevronDown, ChevronRight, TextSelect, X } from "@lucide/vue";
|
||||
import { collectEditorSearchMatches, countEditorSearchMatches, createEditorSearchQuery, replaceEditorSearchMatches, type EditorSearchMatch } from "@/lib/editor/editorSearchQuery";
|
||||
import { appendSearchMatchSelection, findSearchMatch, isSearchAddSelectionModifier, selectionRangesForSearchMatches, type EditorSearchSelectionDirection } from "@/lib/editor/editorSearchSelection";
|
||||
import { useSettingsStore } from "@/stores/settingsStore";
|
||||
|
||||
const props = defineProps<{
|
||||
view: EditorView | null;
|
||||
@@ -13,6 +15,7 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
const searchVisible = ref(false);
|
||||
const searchText = ref("");
|
||||
@@ -24,7 +27,7 @@ const matchCount = ref(0);
|
||||
const currentMatchIndex = ref(0);
|
||||
const searchInputRef = ref<HTMLInputElement>();
|
||||
const replaceInputRef = ref<HTMLInputElement>();
|
||||
const matchCountLimited = ref(false);
|
||||
const selectionLimitReached = ref(false);
|
||||
|
||||
// Scoped search: restrict find/replace to the original selection range
|
||||
let searchScopeFrom: number | null = null;
|
||||
@@ -33,11 +36,13 @@ const inSelectionScope = ref(false);
|
||||
|
||||
const SEARCH_UPDATE_DELAY_MS = 120;
|
||||
const DOCUMENT_SEARCH_UPDATE_DELAY_MS = 500;
|
||||
const MATCH_COUNT_LIMIT = 1000;
|
||||
|
||||
let searchUpdateTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
let documentSearchUpdateTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
function searchMatchLimit(): number {
|
||||
return settingsStore.editorSettings.regexMaxMatchCount;
|
||||
}
|
||||
|
||||
function clearDocumentSearchUpdate() {
|
||||
if (!documentSearchUpdateTimer) return;
|
||||
clearTimeout(documentSearchUpdateTimer);
|
||||
@@ -72,7 +77,7 @@ function clearSearchQuery() {
|
||||
});
|
||||
matchCount.value = 0;
|
||||
currentMatchIndex.value = 0;
|
||||
matchCountLimited.value = false;
|
||||
selectionLimitReached.value = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,7 +103,7 @@ function computeReplacementForMatch(v: EditorView, matchFrom: number, matchTo: n
|
||||
/**
|
||||
* Collect all search matches within the scoped range.
|
||||
*/
|
||||
function collectScopedMatches(v: EditorView, limit = MATCH_COUNT_LIMIT) {
|
||||
function collectScopedMatches(v: EditorView, limit = Number.POSITIVE_INFINITY) {
|
||||
if (searchScopeFrom == null || searchScopeTo == null) return null;
|
||||
const q = createEditorSearchQuery({
|
||||
search: searchText.value,
|
||||
@@ -109,6 +114,33 @@ function collectScopedMatches(v: EditorView, limit = MATCH_COUNT_LIMIT) {
|
||||
return collectEditorSearchMatches(q, v.state, searchScopeFrom, searchScopeTo, limit);
|
||||
}
|
||||
|
||||
function collectAllMatches(v: EditorView, limit = Number.POSITIVE_INFINITY) {
|
||||
if (!searchText.value) return [];
|
||||
const query = createEditorSearchQuery({
|
||||
search: searchText.value,
|
||||
caseSensitive: caseSensitive.value,
|
||||
useRegex: useRegex.value,
|
||||
});
|
||||
if (!query.valid) return [];
|
||||
return collectEditorSearchMatches(query, v.state, searchScopeFrom ?? 0, searchScopeTo ?? v.state.doc.length, limit);
|
||||
}
|
||||
|
||||
function* iterateAllMatches(v: EditorView): Generator<EditorSearchMatch> {
|
||||
if (!searchText.value) return;
|
||||
const query = createEditorSearchQuery({
|
||||
search: searchText.value,
|
||||
caseSensitive: caseSensitive.value,
|
||||
useRegex: useRegex.value,
|
||||
});
|
||||
if (!query.valid) return;
|
||||
const from = searchScopeFrom ?? 0;
|
||||
const to = searchScopeTo ?? v.state.doc.length;
|
||||
const cursor = query.getCursor(v.state);
|
||||
for (let result = cursor.next(); !result.done; result = cursor.next()) {
|
||||
if (result.value.from >= from && result.value.to <= to) yield { from: result.value.from, to: result.value.to };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find next/previous match within the scoped range.
|
||||
* Returns true if a match was found.
|
||||
@@ -116,18 +148,9 @@ function collectScopedMatches(v: EditorView, limit = MATCH_COUNT_LIMIT) {
|
||||
function findInScope(direction: "next" | "prev"): boolean {
|
||||
const v = props.view;
|
||||
if (!v || !searchText.value || searchScopeFrom == null || searchScopeTo == null) return false;
|
||||
const matches = collectScopedMatches(v);
|
||||
if (!matches || matches.length === 0) return false;
|
||||
|
||||
const cursor = v.state.selection.main.head;
|
||||
let target: { from: number; to: number } | null = null;
|
||||
|
||||
if (direction === "next") {
|
||||
target = matches.find((m) => m.from >= cursor) ?? matches.find((m) => m.from >= searchScopeFrom!) ?? null;
|
||||
} else {
|
||||
const before = matches.filter((m) => m.to <= cursor);
|
||||
target = before.length > 0 ? before[before.length - 1] : matches[matches.length - 1];
|
||||
}
|
||||
const selection = v.state.selection.main;
|
||||
const cursor = direction === "next" ? selection.head : selection.from;
|
||||
const target = findSearchMatch(iterateAllMatches(v), cursor, direction);
|
||||
|
||||
if (target) {
|
||||
v.dispatch({
|
||||
@@ -144,27 +167,24 @@ function updateMatchInfo(autoSelect = false) {
|
||||
if (!v || !searchText.value) {
|
||||
matchCount.value = 0;
|
||||
currentMatchIndex.value = 0;
|
||||
matchCountLimited.value = false;
|
||||
return;
|
||||
}
|
||||
if (selectionLimitReached.value && v.state.selection.ranges.length !== searchMatchLimit()) selectionLimitReached.value = false;
|
||||
try {
|
||||
// Scoped: use custom find logic
|
||||
if (searchScopeFrom != null && searchScopeTo != null) {
|
||||
if (autoSelect) findInScope("next");
|
||||
const matches = collectScopedMatches(v);
|
||||
if (!matches) {
|
||||
matchCount.value = 0;
|
||||
currentMatchIndex.value = 0;
|
||||
matchCountLimited.value = false;
|
||||
return;
|
||||
}
|
||||
const count = matches.length;
|
||||
matchCount.value = count;
|
||||
matchCountLimited.value = count >= MATCH_COUNT_LIMIT;
|
||||
const q = createEditorSearchQuery({
|
||||
search: searchText.value,
|
||||
caseSensitive: caseSensitive.value,
|
||||
useRegex: useRegex.value,
|
||||
});
|
||||
if (!q.valid) return;
|
||||
const selFrom = v.state.selection.main.from;
|
||||
const selTo = v.state.selection.main.to;
|
||||
const idx = matches.findIndex((m) => m.from === selFrom && m.to === selTo);
|
||||
currentMatchIndex.value = idx >= 0 ? idx + 1 : count > 0 ? 1 : 0;
|
||||
const { count, currentIndex } = countEditorSearchMatches(q, v.state, searchScopeFrom, searchScopeTo, { from: selFrom, to: selTo });
|
||||
matchCount.value = count;
|
||||
currentMatchIndex.value = currentIndex || (count > 0 ? 1 : 0);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -177,31 +197,19 @@ function updateMatchInfo(autoSelect = false) {
|
||||
if (!q.valid) {
|
||||
matchCount.value = 0;
|
||||
currentMatchIndex.value = 0;
|
||||
matchCountLimited.value = false;
|
||||
return;
|
||||
}
|
||||
if (autoSelect) {
|
||||
cmFindNext(v);
|
||||
}
|
||||
const iter = q.getCursor(v.state);
|
||||
let count = 0;
|
||||
let curIdx = 0;
|
||||
const selFrom = v.state.selection.main.from;
|
||||
const selTo = v.state.selection.main.to;
|
||||
let r = iter.next();
|
||||
while (!r.done) {
|
||||
count++;
|
||||
if (r.value.from === selFrom && r.value.to === selTo) curIdx = count;
|
||||
if (count >= MATCH_COUNT_LIMIT) break;
|
||||
r = iter.next();
|
||||
}
|
||||
const { count, currentIndex } = countEditorSearchMatches(q, v.state, 0, v.state.doc.length, { from: selFrom, to: selTo });
|
||||
matchCount.value = count;
|
||||
matchCountLimited.value = count >= MATCH_COUNT_LIMIT && !r.done;
|
||||
currentMatchIndex.value = curIdx || (count > 0 ? 1 : 0);
|
||||
currentMatchIndex.value = currentIndex || (count > 0 ? 1 : 0);
|
||||
} catch {
|
||||
matchCount.value = 0;
|
||||
currentMatchIndex.value = 0;
|
||||
matchCountLimited.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,6 +223,7 @@ function scheduleSearchUpdate(autoSelect = false) {
|
||||
clearSearchQuery();
|
||||
return;
|
||||
}
|
||||
selectionLimitReached.value = false;
|
||||
dispatchSearchQuery();
|
||||
searchUpdateTimer = setTimeout(() => {
|
||||
searchUpdateTimer = null;
|
||||
@@ -286,7 +295,37 @@ function closeSearch() {
|
||||
return wasVisible;
|
||||
}
|
||||
|
||||
function nextMatch() {
|
||||
function selectAllMatches() {
|
||||
const v = props.view;
|
||||
if (!v) return false;
|
||||
const limit = searchMatchLimit();
|
||||
const matches = collectAllMatches(v, limit + 1);
|
||||
selectionLimitReached.value = matches.length > limit;
|
||||
const ranges = selectionRangesForSearchMatches(matches.slice(0, limit));
|
||||
if (ranges.length === 0) return false;
|
||||
v.dispatch({ selection: EditorSelection.create(ranges), scrollIntoView: true });
|
||||
updateMatchInfo();
|
||||
v.focus();
|
||||
return true;
|
||||
}
|
||||
|
||||
function appendMatch(direction: EditorSearchSelectionDirection) {
|
||||
const v = props.view;
|
||||
if (!v) return false;
|
||||
const selection = appendSearchMatchSelection(v.state.selection, iterateAllMatches(v), direction);
|
||||
if (!selection) return false;
|
||||
v.dispatch({ selection, scrollIntoView: true });
|
||||
updateMatchInfo();
|
||||
v.focus();
|
||||
return true;
|
||||
}
|
||||
|
||||
function nextMatch(event?: MouseEvent) {
|
||||
if (event && isSearchAddSelectionModifier(event)) {
|
||||
event.preventDefault();
|
||||
appendMatch("next");
|
||||
return;
|
||||
}
|
||||
const v = props.view;
|
||||
if (!v || !searchText.value) return;
|
||||
if (searchScopeFrom != null) {
|
||||
@@ -297,7 +336,12 @@ function nextMatch() {
|
||||
updateMatchInfo();
|
||||
}
|
||||
|
||||
function prevMatch() {
|
||||
function prevMatch(event?: MouseEvent) {
|
||||
if (event && isSearchAddSelectionModifier(event)) {
|
||||
event.preventDefault();
|
||||
appendMatch("prev");
|
||||
return;
|
||||
}
|
||||
const v = props.view;
|
||||
if (!v || !searchText.value) return;
|
||||
if (searchScopeFrom != null) {
|
||||
@@ -443,9 +487,18 @@ defineExpose({
|
||||
.*
|
||||
</button>
|
||||
</div>
|
||||
<span class="min-w-[3.4rem] shrink-0 text-center text-xs" :class="searchText && matchCount === 0 ? 'text-destructive' : 'text-muted-foreground'">
|
||||
{{ searchText && matchCount > 0 ? `${currentMatchIndex}/${matchCount}${matchCountLimited ? "+" : ""}` : t("editor.search.noResults") }}
|
||||
<span class="min-w-[3.4rem] shrink-0 text-center text-xs" :class="searchText && matchCount === 0 ? 'text-destructive' : 'text-muted-foreground'" aria-live="polite">
|
||||
{{ selectionLimitReached ? t("editor.search.selectionLimitSummary", { limit: searchMatchLimit(), total: matchCount }) : searchText && matchCount > 0 ? `${currentMatchIndex}/${matchCount}` : t("editor.search.noResults") }}
|
||||
</span>
|
||||
<button
|
||||
class="flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-foreground disabled:cursor-not-allowed disabled:opacity-40 disabled:hover:bg-transparent"
|
||||
:disabled="!searchText || matchCount === 0"
|
||||
:title="selectionLimitReached ? t('editor.search.selectionTruncated', { limit: searchMatchLimit(), total: matchCount }) : t('editor.search.selectAllLimit', { limit: searchMatchLimit() })"
|
||||
:aria-label="selectionLimitReached ? t('editor.search.selectionTruncated', { limit: searchMatchLimit(), total: matchCount }) : t('editor.search.selectAllLimit', { limit: searchMatchLimit() })"
|
||||
@click="selectAllMatches"
|
||||
>
|
||||
<TextSelect class="h-4 w-4" />
|
||||
</button>
|
||||
<span v-if="inSelectionScope" class="shrink-0 rounded bg-accent px-1 py-0.5 text-[10px] font-medium text-muted-foreground" :title="t('editor.search.inSelection')">
|
||||
{{ t("editor.search.inSelection") }}
|
||||
</span>
|
||||
|
||||
@@ -336,6 +336,7 @@ const editDataGridAutoTransposeSingleRow = ref(settingsStore.editorSettings.data
|
||||
const editTableOpenPageSize = ref(settingsStore.editorSettings.tableOpenPageSize);
|
||||
const editInfiniteScroll = ref(settingsStore.editorSettings.infiniteScroll);
|
||||
const editInfiniteScrollMaxRows = ref(settingsStore.editorSettings.infiniteScrollMaxRows);
|
||||
const editRegexMaxMatchCount = ref(settingsStore.editorSettings.regexMaxMatchCount);
|
||||
const editAutoCalculateTotalRows = ref(settingsStore.editorSettings.autoCalculateTotalRows);
|
||||
const editTableColumnTemplateRows = ref<TableColumnTemplateGridRow[]>(tableColumnTemplateRowsFromSettings(settingsStore.editorSettings.tableColumnTemplateFields));
|
||||
const editTableColumnTemplateDatabaseType = ref<DatabaseType>(TABLE_COLUMN_TEMPLATE_DATABASE_TYPES[0] ?? "mysql");
|
||||
@@ -486,6 +487,7 @@ function currentEditorSettingsDraft(): EditorSettingsDraft {
|
||||
tableOpenPageSize: editTableOpenPageSize.value,
|
||||
infiniteScroll: editInfiniteScroll.value,
|
||||
infiniteScrollMaxRows: editInfiniteScrollMaxRows.value,
|
||||
regexMaxMatchCount: editRegexMaxMatchCount.value,
|
||||
autoCalculateTotalRows: editAutoCalculateTotalRows.value,
|
||||
tableColumnTemplateFields: normalizedEditTableColumnTemplateFields.value,
|
||||
shortcuts: editShortcuts.value,
|
||||
@@ -752,6 +754,7 @@ function syncEditorSettingsDraftFromStore() {
|
||||
editTableOpenPageSize.value = settingsStore.editorSettings.tableOpenPageSize;
|
||||
editInfiniteScroll.value = settingsStore.editorSettings.infiniteScroll;
|
||||
editInfiniteScrollMaxRows.value = settingsStore.editorSettings.infiniteScrollMaxRows;
|
||||
editRegexMaxMatchCount.value = settingsStore.editorSettings.regexMaxMatchCount;
|
||||
editAutoCalculateTotalRows.value = settingsStore.editorSettings.autoCalculateTotalRows;
|
||||
editTableColumnTemplateRows.value = tableColumnTemplateRowsFromSettings(settingsStore.editorSettings.tableColumnTemplateFields);
|
||||
editShortcuts.value = normalizeShortcutSettings(settingsStore.editorSettings.shortcuts);
|
||||
@@ -988,6 +991,7 @@ function resetDefaultsForTab(tab: SettingsCategory) {
|
||||
editTableOpenPageSize.value = DEFAULT_EDITOR_SETTINGS.tableOpenPageSize;
|
||||
editInfiniteScroll.value = DEFAULT_EDITOR_SETTINGS.infiniteScroll;
|
||||
editInfiniteScrollMaxRows.value = DEFAULT_EDITOR_SETTINGS.infiniteScrollMaxRows;
|
||||
editRegexMaxMatchCount.value = DEFAULT_EDITOR_SETTINGS.regexMaxMatchCount;
|
||||
editAutoCalculateTotalRows.value = DEFAULT_EDITOR_SETTINGS.autoCalculateTotalRows;
|
||||
editDuckDbWorkerProcessIsolation.value = DEFAULT_DESKTOP_SETTINGS.duckdb_worker_process_isolation;
|
||||
editDuckDbWorkerMaxProcesses.value = DEFAULT_DESKTOP_SETTINGS.duckdb_worker_max_processes;
|
||||
@@ -1050,6 +1054,7 @@ function resetAllDefaults() {
|
||||
editTableOpenPageSize.value = DEFAULT_EDITOR_SETTINGS.tableOpenPageSize;
|
||||
editInfiniteScroll.value = DEFAULT_EDITOR_SETTINGS.infiniteScroll;
|
||||
editInfiniteScrollMaxRows.value = DEFAULT_EDITOR_SETTINGS.infiniteScrollMaxRows;
|
||||
editRegexMaxMatchCount.value = DEFAULT_EDITOR_SETTINGS.regexMaxMatchCount;
|
||||
editAutoCalculateTotalRows.value = DEFAULT_EDITOR_SETTINGS.autoCalculateTotalRows;
|
||||
editTableColumnTemplateRows.value = tableColumnTemplateRowsFromSettings(DEFAULT_EDITOR_SETTINGS.tableColumnTemplateFields);
|
||||
editShortcuts.value = normalizeShortcutSettings(DEFAULT_EDITOR_SETTINGS.shortcuts);
|
||||
@@ -3651,6 +3656,22 @@ onUnmounted(() => {
|
||||
<Switch id="editor-auto-close-brackets" v-model="editAutoCloseBrackets" 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="regex-max-match-count">{{ t("settings.regexMaxMatchCount") }}</Label>
|
||||
<p class="text-xs text-muted-foreground">{{ t("settings.regexMaxMatchCountDescription") }}</p>
|
||||
</div>
|
||||
<Input
|
||||
id="regex-max-match-count"
|
||||
v-model.number="editRegexMaxMatchCount"
|
||||
type="number"
|
||||
inputmode="numeric"
|
||||
:min="100"
|
||||
:max="10000"
|
||||
class="h-7 w-24 px-2 text-xs tabular-nums [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
|
||||
/>
|
||||
</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="editor-insert-space-after-completion">{{ t("settings.insertSpaceAfterCompletion") }}</Label>
|
||||
|
||||
@@ -89,6 +89,7 @@ import { createDbxCodeMirrorSqlDialect, type CodeMirrorSqlDialectName } from "@/
|
||||
import { sqlSemanticTableNameSpansForSyntaxTree } from "@/lib/editor/codemirrorSqlSemanticHighlight";
|
||||
import { startsQueryEditorRectangularSelection, usesQueryEditorObjectNavigationModifier } from "@/lib/editor/queryEditorPointerSelection";
|
||||
import { LARGE_PASTE_HISTORY_USER_EVENT, normalizeQueryEditorPasteText, recoverableNativePasteSuffix, shouldRecoverLargeTauriPaste } from "@/lib/editor/queryEditorLargePaste";
|
||||
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 { metadataSchemaForConnection, sqlSnippetDatabaseTypeForConnection } from "@/lib/database/jdbcDialect";
|
||||
@@ -1439,6 +1440,7 @@ function runKeymapExtension(codeMirrorKeymap: (typeof import("@codemirror/view")
|
||||
...binding(shortcuts.undo, (view) => codeMirrorUndo?.(view) ?? false),
|
||||
...binding(shortcuts.redo, (view) => codeMirrorRedo?.(view) ?? false),
|
||||
...binding(shortcuts.selectAll, (view) => codeMirrorSelectAll?.(view) ?? false),
|
||||
...binding(shortcuts.extendSelection, extendQueryEditorSelectionForView),
|
||||
...binding(shortcuts.uppercaseSelection, () => convertSelectedSqlCase("upper")),
|
||||
...binding(shortcuts.lowercaseSelection, () => convertSelectedSqlCase("lower")),
|
||||
...binding(shortcuts.toggleLineComment, (view) => codeMirrorToggleLineComment?.(view) ?? false),
|
||||
@@ -1469,6 +1471,16 @@ function runKeymapExtension(codeMirrorKeymap: (typeof import("@codemirror/view")
|
||||
];
|
||||
}
|
||||
|
||||
function extendQueryEditorSelectionForView(currentView: EditorViewType): boolean {
|
||||
const databaseType = props.databaseType;
|
||||
const language = databaseType === "redis" || databaseType === "mongodb" || databaseType === "elasticsearch" ? "text" : "sql";
|
||||
return extendQueryEditorSelection(currentView, {
|
||||
databaseType,
|
||||
dialect: props.syntaxDialect ?? props.dialect,
|
||||
language,
|
||||
});
|
||||
}
|
||||
|
||||
function acceptCompletionOrNextSnippetField(view: EditorViewType): boolean {
|
||||
const completionStatus = codeMirrorCompletionStatus?.(view.state) ?? null;
|
||||
if (completionStatus === "active" && (codeMirrorAcceptCompletion?.(view) ?? false)) return true;
|
||||
@@ -4130,6 +4142,14 @@ onMounted(async () => {
|
||||
]),
|
||||
),
|
||||
runKeymapComp.of(runKeymapExtension(keymap)),
|
||||
Prec.highest(
|
||||
EditorView.domEventHandlers({
|
||||
keydown(event, currentView) {
|
||||
const shortcuts = normalizeShortcutSettings(settingsStore.editorSettings.shortcuts);
|
||||
return runQueryEditorAltExtendSelection(event, shortcuts.extendSelection, currentView, extendQueryEditorSelectionForView);
|
||||
},
|
||||
}),
|
||||
),
|
||||
wordWrapComp.of(props.forceWordWrap || initialSettings.wordWrap ? EditorView.lineWrapping : []),
|
||||
readOnlyComp.of([EditorState.readOnly.of(!!props.readOnly), EditorView.editable.of(!props.readOnly)]),
|
||||
indentComp.of(indentExtension()),
|
||||
|
||||
@@ -375,6 +375,7 @@ async function mountConfigEditor() {
|
||||
},
|
||||
}),
|
||||
basicSetup,
|
||||
EditorState.allowMultipleSelections.of(true),
|
||||
trimmedSelectionLayer(),
|
||||
Prec.highest(keymap.of([{ key: "Mod-f", run: () => configSearchPanelRef.value?.openSearch() ?? false, preventDefault: true }, { key: "Mod-h", run: () => configSearchPanelRef.value?.openReplace() ?? false, preventDefault: true }, indentWithTab])),
|
||||
keymap.of([...defaultKeymap, ...historyKeymap]),
|
||||
|
||||
@@ -121,6 +121,7 @@ async function initDdlEditor(content: string) {
|
||||
},
|
||||
}),
|
||||
basicSetup,
|
||||
EditorState.allowMultipleSelections.of(true),
|
||||
langSql.sql({ dialect }),
|
||||
themeExt,
|
||||
fontExt,
|
||||
|
||||
Reference in new issue
Block a user