fix(editor): improve ASE SQL semantic highlighting
This commit is contained in:
14 files changed
+360
-22
No files matched your search
@@ -13,7 +13,7 @@ import { resolveExecutableSql, type SqlExecutionSnapshot, type SqlExecutionOverr
|
||||
import { buildExecutionCandidates, hasMultipleExecutionTargets, supportsExecutionTargetPicker, type SqlTextRange } from "@/lib/sql/sqlStatementRanges";
|
||||
import { executableStatementRangeAtCursor, executableStatementRangeCacheForDoc, executableStatementRangeStartingAt as executableStatementRangeStartingAtLine, type ExecutableStatementRangeCache } from "@/lib/sql/executableStatementRangeCache";
|
||||
import { currentStatementFrameRangeTo, visualSqlColumnsWithInlineHints } from "@/lib/sql/currentStatementFrame";
|
||||
import { parseInsertValueHints } from "@/lib/sql/insertValueHints";
|
||||
import { expandToSqlStatementWindow, parseInsertValueHints } from "@/lib/sql/insertValueHints";
|
||||
import { formatSqlText, type SqlFormatDialect } from "@/lib/sql/sqlFormatter";
|
||||
import { buildSqlInConditionFromPasteSource, insertTextForSqlInCondition } from "@/lib/sql/sqlInListPaste";
|
||||
import { resolveSqlSingleQuoteKeyAction } from "@/lib/sql/sqlQuoteCaret";
|
||||
@@ -53,7 +53,7 @@ import {
|
||||
type QueryEditorTableReferenceDropDetail,
|
||||
type QueryEditorTableReferencePayload,
|
||||
} from "@/lib/editor/queryEditorTableDrop";
|
||||
import { EDITOR_FONT_FAMILY_CSS_VAR, EDITOR_FONT_SIZE_CSS_VAR, createRunStatementButtonDom, loadEditorTheme, editorFontTheme, sqlCompletionTheme } from "@/lib/editor/editorThemes";
|
||||
import { EDITOR_FONT_FAMILY_CSS_VAR, EDITOR_FONT_SIZE_CSS_VAR, createRunStatementButtonDom, loadEditorTheme, editorFontTheme, sqlCompletionTheme, sqlSemanticHighlightTheme } from "@/lib/editor/editorThemes";
|
||||
import { clampEditorFontSize, createEditorZoomCommitScheduler, fontSizeFromGestureScale, fontSizeFromWheelDelta } from "@/lib/editor/editorZoom";
|
||||
import { normalizeShortcutSettings, shortcutToCodeMirrorKey } from "@/lib/editor/shortcutRegistry";
|
||||
import { trimmedSelectionLayer } from "@/lib/editor/codemirrorTrimmedSelectionLayer";
|
||||
@@ -61,6 +61,7 @@ import { selectionMatchOccurrences } from "@/lib/editor/codemirrorSelectionMatch
|
||||
import { createInsertValueHintsExtension, requestInsertValueHintsRefresh } from "@/lib/editor/codemirrorInsertValueHints";
|
||||
import { focusEditorView } from "@/lib/editor/queryEditorFocus";
|
||||
import { createDbxCodeMirrorSqlDialect } from "@/lib/editor/codemirrorSqlDialect";
|
||||
import { sqlSemanticTableNameSpansForSyntaxTree } from "@/lib/editor/codemirrorSqlSemanticHighlight";
|
||||
import { startsQueryEditorRectangularSelection } from "@/lib/editor/queryEditorPointerSelection";
|
||||
import type { StatementExecutionMarker } from "@/lib/tabs/tabPresentation";
|
||||
import { isSchemaAware, isSingleDatabase, supportsSqlInListPaste } from "@/lib/database/databaseFeatureSupport";
|
||||
@@ -80,6 +81,7 @@ const props = defineProps<{
|
||||
schema?: string;
|
||||
databaseType?: DatabaseType;
|
||||
dialect?: "mysql" | "postgres" | "sqlserver";
|
||||
syntaxDialect?: "mysql" | "postgres" | "sqlserver";
|
||||
formatDialect?: SqlFormatDialect;
|
||||
formatRequestId?: number;
|
||||
executionError?: string;
|
||||
@@ -216,6 +218,7 @@ let wordWrapComp: import("@codemirror/state").Compartment | null = null;
|
||||
let vimModeComp: import("@codemirror/state").Compartment | null = null;
|
||||
let closeBracketsComp: import("@codemirror/state").Compartment | null = null;
|
||||
let sqlLanguageComp: import("@codemirror/state").Compartment | null = null;
|
||||
let sqlSemanticHighlightComp: import("@codemirror/state").Compartment | null = null;
|
||||
let codeMirrorCloseBrackets: typeof import("@codemirror/autocomplete").closeBrackets | null = null;
|
||||
let codeMirrorCloseBracketsKeymap: readonly import("@codemirror/view").KeyBinding[] | null = null;
|
||||
let readOnlyComp: import("@codemirror/state").Compartment | null = null;
|
||||
@@ -233,6 +236,7 @@ let buildSqlDiagnosticExtension: (() => import("@codemirror/state").Extension) |
|
||||
let buildSqlSignatureExtension: (() => import("@codemirror/state").Extension) | null = null;
|
||||
let buildSqlCompletionExtension: (() => import("@codemirror/state").Extension) | null = null;
|
||||
let buildSqlLanguageExtension: (() => import("@codemirror/state").Extension) | null = null;
|
||||
let buildSqlSemanticHighlightExtension: (() => import("@codemirror/state").Extension) | null = null;
|
||||
let codeMirrorSnippetCompletion: typeof import("@codemirror/autocomplete").snippetCompletion;
|
||||
let codeMirrorCompletionStatus: typeof import("@codemirror/autocomplete").completionStatus | null = null;
|
||||
let codeMirrorAcceptCompletion: typeof import("@codemirror/autocomplete").acceptCompletion | null = null;
|
||||
@@ -1380,7 +1384,7 @@ async function resolveSqlHoverTooltip(currentView: EditorViewType, pos: number)
|
||||
const parts = splitQualifiedIdentifier(identifier);
|
||||
const name = parts[parts.length - 1] ?? identifier;
|
||||
const qualifier = parts.length > 1 ? parts[parts.length - 2] : undefined;
|
||||
const semanticModel = SEMANTIC_SQL_COMPLETION_ENABLED ? buildSqlSemanticModel(sql, pos, { databaseType: props.databaseType, dialect: props.dialect }) : null;
|
||||
const semanticModel = SEMANTIC_SQL_COMPLETION_ENABLED ? buildSqlSemanticModel(sql, pos, { databaseType: props.databaseType, dialect: props.syntaxDialect ?? props.dialect }) : null;
|
||||
const semanticTarget = semanticModel ? resolveSqlSemanticNavigationTarget(semanticModel, parts) : null;
|
||||
const semanticQualifierIsRowSource = !!qualifier && !!semanticTarget && (semanticTarget.alias?.toLowerCase() === qualifier.toLowerCase() || semanticTarget.source.name.toLowerCase() === qualifier.toLowerCase());
|
||||
const tableLookupName = semanticTarget && !semanticQualifierIsRowSource ? semanticTarget.name : name;
|
||||
@@ -1671,7 +1675,7 @@ async function refreshSemanticDiagnostics(options: { preserveOutsideRanges?: boo
|
||||
if (runId !== semanticDiagnosticRunId) return;
|
||||
|
||||
const semanticCursor = Math.max(0, Math.min(currentView.state.selection.main.head - range.from, range.sql.length));
|
||||
const semanticModel = SEMANTIC_SQL_COMPLETION_ENABLED ? buildSqlSemanticModel(range.sql, semanticCursor, { databaseType: props.databaseType, dialect: props.dialect }) : null;
|
||||
const semanticModel = SEMANTIC_SQL_COMPLETION_ENABLED ? buildSqlSemanticModel(range.sql, semanticCursor, { databaseType: props.databaseType, dialect: props.syntaxDialect ?? props.dialect }) : null;
|
||||
const semanticAnalysis = semanticModel ? mergeSqlSemanticReferenceAnalysis(analysis, semanticModel) : analysis;
|
||||
const { tables, missingTables } = await enrichSemanticDiagnosticTables(semanticAnalysis.tables);
|
||||
const columnMetadataMissingTables = await ensureColumnsForSemanticDiagnostics(tables);
|
||||
@@ -2034,7 +2038,7 @@ async function provideSqlCompletions(context: CompletionContext) {
|
||||
if (!explicit && !shouldAutoOpenSqlCompletion(fullDoc, position)) return null;
|
||||
|
||||
const legacyCompletionContext = getSqlCompletionContext(fullDoc, position);
|
||||
const semanticModel = SEMANTIC_SQL_COMPLETION_ENABLED ? buildSqlSemanticModel(fullDoc, position, { databaseType: props.databaseType, dialect: props.dialect }) : null;
|
||||
const semanticModel = SEMANTIC_SQL_COMPLETION_ENABLED ? buildSqlSemanticModel(fullDoc, position, { databaseType: props.databaseType, dialect: props.syntaxDialect ?? props.dialect }) : null;
|
||||
const completionContext = semanticModel ? sqlCompletionContextFromSemantic(semanticModel, legacyCompletionContext) : legacyCompletionContext;
|
||||
|
||||
if (!hasDatabase) {
|
||||
@@ -2686,7 +2690,7 @@ onMounted(async () => {
|
||||
langSql,
|
||||
{ autocompletion, startCompletion, acceptCompletion, closeBrackets, closeBracketsKeymap, snippetCompletion, completionStatus, completionKeymap, insertCompletionText, nextSnippetField },
|
||||
{ copyLineDown, copyLineUp, deleteLine, indentLess, indentMore, insertNewlineKeepIndent, moveLineDown, moveLineUp, redo, selectAll, undo, toggleLineComment, history, defaultKeymap, historyKeymap },
|
||||
{ bracketMatching, foldGutter, indentOnInput, indentUnit, syntaxHighlighting, defaultHighlightStyle, foldKeymap },
|
||||
{ bracketMatching, foldGutter, indentOnInput, indentUnit, syntaxHighlighting, defaultHighlightStyle, foldKeymap, 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")]);
|
||||
editorViewModule = { EditorView, keymap, rectangularSelection } as typeof import("@codemirror/view");
|
||||
@@ -2699,6 +2703,7 @@ onMounted(async () => {
|
||||
vimModeComp = new Compartment();
|
||||
closeBracketsComp = new Compartment();
|
||||
sqlLanguageComp = new Compartment();
|
||||
sqlSemanticHighlightComp = new Compartment();
|
||||
codeMirrorCloseBrackets = closeBrackets;
|
||||
codeMirrorCloseBracketsKeymap = closeBracketsKeymap;
|
||||
readOnlyComp = new Compartment();
|
||||
@@ -2929,7 +2934,46 @@ onMounted(async () => {
|
||||
override: [async (context: CompletionContext) => provideSqlCompletions(context)],
|
||||
});
|
||||
|
||||
buildSqlLanguageExtension = () => langSql.sql({ dialect: createDbxCodeMirrorSqlDialect(langSql, props.dialect, props.databaseType) });
|
||||
buildSqlLanguageExtension = () => langSql.sql({ dialect: createDbxCodeMirrorSqlDialect(langSql, props.syntaxDialect ?? props.dialect, props.databaseType) });
|
||||
buildSqlSemanticHighlightExtension = () => [
|
||||
ViewPlugin.fromClass(
|
||||
class {
|
||||
decorations: import("@codemirror/view").DecorationSet;
|
||||
constructor(currentView: import("@codemirror/view").EditorView) {
|
||||
this.decorations = this.buildDecorations(currentView);
|
||||
}
|
||||
update(update: import("@codemirror/view").ViewUpdate) {
|
||||
if (update.docChanged || update.viewportChanged) this.decorations = this.buildDecorations(update.view);
|
||||
}
|
||||
buildDecorations(currentView: import("@codemirror/view").EditorView) {
|
||||
const sql = currentView.state.doc.toString();
|
||||
const windows: Array<{ from: number; to: number }> = [];
|
||||
for (const visibleRange of currentView.visibleRanges) {
|
||||
const next = expandToSqlStatementWindow(sql, visibleRange.from, visibleRange.to);
|
||||
const previous = windows[windows.length - 1];
|
||||
if (previous && next.from <= previous.to) previous.to = Math.max(previous.to, next.to);
|
||||
else windows.push(next);
|
||||
}
|
||||
const tree = ensureSyntaxTree(currentView.state, windows[windows.length - 1]?.to ?? 0, 25);
|
||||
if (!tree) return Decoration.set([]);
|
||||
const ranges = windows.flatMap((window) => sqlSemanticTableNameSpansForSyntaxTree(sql, window, tree, { databaseType: props.databaseType, dialect: props.syntaxDialect ?? props.dialect }));
|
||||
return Decoration.set(
|
||||
ranges.map((range) =>
|
||||
Decoration.mark({
|
||||
class: "cm-sql-table-name",
|
||||
attributes: {
|
||||
"data-sql-token": "table",
|
||||
},
|
||||
}).range(range.start, range.end),
|
||||
),
|
||||
true,
|
||||
);
|
||||
}
|
||||
},
|
||||
{ decorations: (value) => value.decorations },
|
||||
),
|
||||
sqlSemanticHighlightTheme(EditorView),
|
||||
];
|
||||
|
||||
const initialSettings = settingsStore.editorSettings;
|
||||
const theme = await loadEditorTheme(initialSettings.theme, editorThemeAppearance(), getCurrentCustomThemeColors(), themePalette.value);
|
||||
@@ -3086,6 +3130,7 @@ onMounted(async () => {
|
||||
vimModeComp.of(vimModeExtension(initialSettings.vimModeEnabled)),
|
||||
keymap.of([...defaultKeymap, ...searchKeymap, ...historyKeymap, ...foldKeymap, ...completionKeymap]),
|
||||
sqlLanguageComp.of(buildSqlLanguageExtension()),
|
||||
sqlSemanticHighlightComp.of(buildSqlSemanticHighlightExtension()),
|
||||
tooltips({ parent: document.body }),
|
||||
completionComp.of(buildSqlCompletionExtension()),
|
||||
sqlCompletionTheme(EditorView),
|
||||
@@ -3434,10 +3479,10 @@ watch(
|
||||
},
|
||||
);
|
||||
|
||||
watch([() => props.databaseType, () => props.dialect], () => {
|
||||
watch([() => props.databaseType, () => props.dialect, () => props.syntaxDialect], () => {
|
||||
executableStatementRangeCache = null;
|
||||
if (!view.value || !sqlLanguageComp || !buildSqlLanguageExtension) return;
|
||||
view.value.dispatch({ effects: sqlLanguageComp.reconfigure(buildSqlLanguageExtension()) });
|
||||
if (!view.value || !sqlLanguageComp || !buildSqlLanguageExtension || !sqlSemanticHighlightComp || !buildSqlSemanticHighlightExtension) return;
|
||||
view.value.dispatch({ effects: [sqlLanguageComp.reconfigure(buildSqlLanguageExtension()), sqlSemanticHighlightComp.reconfigure(buildSqlSemanticHighlightExtension())] });
|
||||
});
|
||||
|
||||
watch(
|
||||
|
||||
Reference in new issue
Block a user