Line data Source code
1 : import * as dashql from '@ankoh/dashql-core';
2 :
3 : import { ScriptData, ScriptKey } from './session_scripts.js';
4 : import { VariantKind } from '../../utils/variant.js';
5 : import { QUALIFIED_DATABASE_ID, QUALIFIED_SCHEMA_ID, QUALIFIED_TABLE_COLUMN_ID, QUALIFIED_TABLE_ID, QualifiedCatalogObjectID } from './catalog_object_id.js';
6 :
7 : export interface FocusedExpression {
8 : /// The expression id
9 : expression: dashql.ExternalObjectID.Value;
10 : }
11 : export interface FocusedTableRef {
12 : /// The table ref
13 : tableReference: dashql.ExternalObjectID.Value;
14 : }
15 :
16 : export interface FocusedCompletion {
17 : /// The completion
18 : completion: dashql.FlatBufferPtr<dashql.buffers.completion.Completion>;
19 : /// The index of the selected completion candidate
20 : completionCandidateIndex: number;
21 : }
22 :
23 1 : export const FOCUSED_TABLE_REF_ID = Symbol('FOCUSED_TABLE_REF_ID');
24 1 : export const FOCUSED_EXPRESSION_ID = Symbol('FOCUSED_EXPRESSION_ID');
25 1 : export const FOCUSED_COMPLETION = Symbol('FOCUSED_COMPLETION');
26 :
27 : export type FocusTarget =
28 : QualifiedCatalogObjectID
29 : | VariantKind<typeof FOCUSED_TABLE_REF_ID, FocusedTableRef>
30 : | VariantKind<typeof FOCUSED_EXPRESSION_ID, FocusedExpression>
31 : | VariantKind<typeof FOCUSED_COMPLETION, FocusedCompletion>
32 : ;
33 :
34 1 : export enum FocusType {
35 1 : COMPLETION_CANDIDATE,
36 1 : CATALOG_ENTRY,
37 1 : COLUMN_REF,
38 1 : COLUMN_REF_UNDER_CURSOR,
39 1 : COLUMN_REF_OF_TARGET_TABLE,
40 1 : COLUMN_REF_OF_TARGET_COLUMN,
41 1 : COLUMN_REF_OF_PEER_COLUMN,
42 1 : TABLE_REF,
43 1 : TABLE_REF_UNDER_CURSOR,
44 1 : TABLE_REF_OF_TARGET_TABLE,
45 1 : TABLE_REF_OF_TARGET_COLUMN,
46 1 : }
47 :
48 : export interface SemanticUserFocus {
49 : /// The input focus target
50 : focusTarget: FocusTarget;
51 : /// The focused catalog object
52 : catalogObject: (QualifiedCatalogObjectID & { focus: FocusType }) | null;
53 : /// The column references in the script, referencing the catalog object
54 : scriptColumnRefs: Map<dashql.ExternalObjectID.Value, FocusType>;
55 : /// The table references in the script, referencing the catalog object
56 : scriptTableRefs: Map<dashql.ExternalObjectID.Value, FocusType>;
57 : }
58 :
59 1 : export function deriveFocusFromEditorUpdate(
60 1 : scriptKey: ScriptKey,
61 1 : update: dashql.buffers.editor.EditorUpdateT | null | undefined,
62 1 : ): SemanticUserFocus | null {
63 1 : const context = update?.primaryCursorContext;
64 1 : if (context == null || context.kind === dashql.buffers.editor.EditorCursorSemanticKind.NONE) {
65 1 : return null;
66 : }
67 1 : const isTable = context.kind === dashql.buffers.editor.EditorCursorSemanticKind.TABLE_REFERENCE;
68 1 : const focusTarget: FocusTarget = isTable
69 1 : ? {
70 1 : type: FOCUSED_TABLE_REF_ID,
71 1 : value: { tableReference: dashql.ExternalObjectID.create(scriptKey, context.referenceId) },
72 1 : }
73 1 : : {
74 1 : type: FOCUSED_EXPRESSION_ID,
75 1 : value: { expression: dashql.ExternalObjectID.create(scriptKey, context.referenceId) },
76 1 : };
77 1 : const focus: SemanticUserFocus = {
78 1 : focusTarget,
79 1 : catalogObject: null,
80 1 : scriptTableRefs: new Map(),
81 1 : scriptColumnRefs: new Map(),
82 1 : };
83 1 : if (context.resolved) {
84 1 : focus.catalogObject = context.kind === dashql.buffers.editor.EditorCursorSemanticKind.COLUMN_REFERENCE
85 1 : ? {
86 1 : type: QUALIFIED_TABLE_COLUMN_ID,
87 1 : value: {
88 1 : database: context.catalogDatabaseId,
89 1 : schema: context.catalogSchemaId,
90 1 : table: context.catalogTableId,
91 1 : column: context.catalogColumnId,
92 1 : referencedCatalogVersion: context.referencedCatalogVersion,
93 1 : },
94 1 : focus: FocusType.COLUMN_REF,
95 1 : }
96 1 : : {
97 1 : type: QUALIFIED_TABLE_ID,
98 1 : value: {
99 1 : database: context.catalogDatabaseId,
100 1 : schema: context.catalogSchemaId,
101 1 : table: context.catalogTableId,
102 1 : referencedCatalogVersion: context.referencedCatalogVersion,
103 1 : },
104 1 : focus: FocusType.TABLE_REF,
105 1 : };
106 : }
107 1 : for (const referenceId of context.relatedTableReferenceIds) {
108 1 : focus.scriptTableRefs.set(
109 1 : dashql.ExternalObjectID.create(scriptKey, referenceId),
110 1 : isTable && referenceId === context.referenceId
111 1 : ? FocusType.TABLE_REF_UNDER_CURSOR
112 1 : : isTable ? FocusType.TABLE_REF_OF_TARGET_TABLE : FocusType.TABLE_REF_OF_TARGET_COLUMN,
113 1 : );
114 : }
115 1 : for (const referenceId of context.relatedColumnReferenceIds) {
116 1 : let focusType = isTable ? FocusType.COLUMN_REF_OF_TARGET_TABLE : FocusType.COLUMN_REF_OF_TARGET_COLUMN;
117 1 : if (!isTable && referenceId === context.referenceId) focusType = FocusType.COLUMN_REF_UNDER_CURSOR;
118 1 : focus.scriptColumnRefs.set(dashql.ExternalObjectID.create(scriptKey, referenceId), focusType);
119 1 : }
120 1 : return focus;
121 1 : }
122 :
123 : /// Derive focus from script completion
124 1 : export function deriveFocusFromCompletionCandidates(
125 1 : _scriptKey: ScriptKey,
126 1 : scriptData: ScriptData,
127 1 : ): SemanticUserFocus | null {
128 1 : if (scriptData.completion == null) {
129 1 : return null;
130 : }
131 1 : const completion = scriptData.completion.buffer.read();
132 1 : if (completion.candidatesLength() == 0 || scriptData.completion.candidateId >= completion.candidatesLength()) {
133 1 : return null;
134 : }
135 :
136 1 : const focusedCandidateId = scriptData.completion.candidateId ?? 0;
137 : const focusTarget: FocusTarget = {
138 1 : type: FOCUSED_COMPLETION,
139 1 : value: {
140 1 : completion: scriptData.completion.buffer,
141 1 : completionCandidateIndex: focusedCandidateId
142 1 : }
143 : };
144 1 : const focus: SemanticUserFocus = {
145 1 : focusTarget,
146 1 : catalogObject: null,
147 1 : scriptTableRefs: new Map(),
148 1 : scriptColumnRefs: new Map(),
149 1 : };
150 :
151 : // Are we focusing a valid catalog object?
152 1 : const candidate = completion.candidates(focusedCandidateId)!;
153 1 : if (scriptData.completion.catalogObjectId >= candidate.catalogObjectsLength()) {
154 1 : return focus;
155 : }
156 1 : const candidateObject = candidate.catalogObjects(scriptData.completion.catalogObjectId)!;
157 :
158 : // Inspect the catalog object and derive a focus target
159 1 : switch (candidateObject.objectType()) {
160 0 : case dashql.buffers.completion.CompletionCandidateObjectType.DATABASE:
161 0 : focus.catalogObject = {
162 0 : type: QUALIFIED_DATABASE_ID,
163 0 : value: {
164 0 : database: candidateObject.catalogDatabaseId()
165 0 : },
166 0 : focus: FocusType.COMPLETION_CANDIDATE
167 0 : };
168 0 : break;
169 0 : case dashql.buffers.completion.CompletionCandidateObjectType.SCHEMA:
170 0 : focus.catalogObject = {
171 0 : type: QUALIFIED_SCHEMA_ID,
172 0 : value: {
173 0 : database: candidateObject.catalogDatabaseId(),
174 0 : schema: candidateObject.catalogSchemaId()
175 0 : },
176 0 : focus: FocusType.COMPLETION_CANDIDATE
177 0 : };
178 0 : break;
179 1 : case dashql.buffers.completion.CompletionCandidateObjectType.TABLE:
180 1 : focus.catalogObject = {
181 1 : type: QUALIFIED_TABLE_ID,
182 1 : value: {
183 1 : database: candidateObject.catalogDatabaseId(),
184 1 : schema: candidateObject.catalogSchemaId(),
185 1 : table: candidateObject.catalogTableId(),
186 1 : referencedCatalogVersion: candidateObject.referencedCatalogVersion(),
187 1 : },
188 1 : focus: FocusType.COMPLETION_CANDIDATE
189 1 : };
190 1 : break;
191 1 : case dashql.buffers.completion.CompletionCandidateObjectType.COLUMN:
192 1 : focus.catalogObject = {
193 1 : type: QUALIFIED_TABLE_COLUMN_ID,
194 1 : value: {
195 1 : database: candidateObject.catalogDatabaseId(),
196 1 : schema: candidateObject.catalogSchemaId(),
197 1 : table: candidateObject.catalogTableId(),
198 1 : column: candidateObject.tableColumnId(),
199 1 : referencedCatalogVersion: candidateObject.referencedCatalogVersion(),
200 1 : },
201 1 : focus: FocusType.COMPLETION_CANDIDATE
202 1 : };
203 1 : break;
204 0 : case dashql.buffers.completion.CompletionCandidateObjectType.FUNCTION:
205 : break;
206 1 : }
207 1 : return focus;
208 1 : }
|