Skip to content

Commit 12ba299

Browse files
committed
feat: move autocomplete logic to library
1 parent 0002529 commit 12ba299

File tree

6 files changed

+138
-697
lines changed

6 files changed

+138
-697
lines changed

src/utils/monaco/yql/autocomplete/generateSuggestions.ts

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import type {FetchedColumn, FetchedEntity} from './types';
1010
import {
1111
getSuggestionIndex,
1212
isVariable,
13+
removeBackticks,
14+
removeStringDuplicates,
1315
suggestionIndexToWeight,
1416
wrapStringToBackticks,
1517
} from './utils';
@@ -31,7 +33,7 @@ export async function generateSimpleTypesSuggestion(
3133
export async function generateEntitiesSuggestion(
3234
rangeToInsertSuggestion: monaco.IRange,
3335
fetchedEntities: FetchedEntity[],
34-
prefix: string,
36+
prefix = '',
3537
): Promise<monaco.languages.CompletionItem[]> {
3638
const withBackticks = prefix?.startsWith('`');
3739

@@ -70,11 +72,8 @@ export async function generateEntitiesSuggestion(
7072

7173
export function generateKeywordsSuggestion(
7274
rangeToInsertSuggestion: monaco.IRange,
73-
suggestKeywords?: KeywordSuggestion[],
75+
suggestKeywords: KeywordSuggestion[] = [],
7476
) {
75-
if (!suggestKeywords) {
76-
return [];
77-
}
7877
return suggestKeywords.map((keywordSuggestion) => ({
7978
label: keywordSuggestion.value,
8079
insertText: keywordSuggestion.value,
@@ -87,11 +86,8 @@ export function generateKeywordsSuggestion(
8786

8887
export function generateVariableSuggestion(
8988
rangeToInsertSuggestion: monaco.IRange,
90-
suggestVariables?: VariableSuggestion[],
89+
suggestVariables: VariableSuggestion[] = [],
9190
) {
92-
if (!suggestVariables) {
93-
return [];
94-
}
9591
return suggestVariables.map(({name}) => {
9692
const variable = '$' + name;
9793
return {
@@ -231,7 +227,7 @@ export async function generateColumnsSuggestion(
231227
const multi = suggestColumns.tables.length > 1;
232228

233229
const tableNames = suggestColumns.tables.map((entity) => entity.name);
234-
const filteredTableNames = Array.from(new Set(tableNames));
230+
const filteredTableNames = removeStringDuplicates(tableNames);
235231

236232
const variableSources = filteredTableNames.filter(isVariable);
237233

@@ -268,7 +264,7 @@ export async function generateColumnsSuggestion(
268264

269265
const tableNameToAliasMap = suggestColumns.tables?.reduce(
270266
(acc, entity) => {
271-
const name = entity.name;
267+
const name = removeBackticks(entity.name);
272268
const aliases = acc[name] ?? [];
273269
if (entity.alias) {
274270
aliases.push(entity.alias);
@@ -282,7 +278,7 @@ export async function generateColumnsSuggestion(
282278
[...fetchedColumns, ...columnsFromVariable, ...predefinedColumns].forEach((col) => {
283279
const normalizedName = wrapStringToBackticks(col.name);
284280

285-
const aliases = tableNameToAliasMap[col.parent];
281+
const aliases = tableNameToAliasMap[removeBackticks(col.parent)];
286282
const currentSuggestionIndex = suggestions.length;
287283
if (aliases?.length) {
288284
aliases.forEach((a) => {

src/utils/monaco/yql/autocomplete/types.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ export type AutocompleteEntityType = YQLEntity | 'directory';
66

77
export type FetchedEntity = {
88
value: string;
9-
autocompleteType?: AutocompleteEntityType;
10-
isDir?: boolean;
9+
isDir: boolean;
1110
detail?: string;
1211
};
1312

src/utils/monaco/yql/autocomplete/utils.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,19 +48,38 @@ export function isVariable(value: string) {
4848
return value.startsWith('$');
4949
}
5050

51+
export function removeStringDuplicates(value: string[] = []) {
52+
return Array.from(new Set(value));
53+
}
54+
5155
export function getEntitiesToFetchColumns(suggestColumns: YqlAutocompleteResult['suggestColumns']) {
5256
const names = suggestColumns?.tables?.map((entity) => entity.name);
5357
// remove duplicates if any
54-
const filteredTableNames = Array.from(new Set(names));
58+
const filteredTableNames = removeStringDuplicates(names);
5559
return filteredTableNames.filter((name) => !isVariable(name));
5660
}
5761

5862
const specialSymbols = /[\s'"-/@]/;
5963

6064
export function wrapStringToBackticks(value: string) {
65+
if (value.startsWith('`') && value.endsWith('`')) {
66+
return value;
67+
}
6168
let result = value;
6269
if (value.match(specialSymbols)) {
6370
result = `\`${value}\``;
6471
}
6572
return result;
6673
}
74+
75+
export function removeBackticks(value: string) {
76+
let sliceStart = 0;
77+
let sliceEnd = value.length;
78+
if (value.startsWith('`')) {
79+
sliceStart = 1;
80+
}
81+
if (value.endsWith('`')) {
82+
sliceEnd = -1;
83+
}
84+
return value.slice(sliceStart, sliceEnd);
85+
}

src/utils/monaco/yql/autocomplete/yqlAutocomplete.ts

Lines changed: 83 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type {CursorPosition as BaseCursorPosition} from '@gravity-ui/websql-autocomplete/shared';
2-
import type {YQLEntity, YqlAutocompleteResult} from '@gravity-ui/websql-autocomplete/yql';
2+
import type {YQLEntity} from '@gravity-ui/websql-autocomplete/yql';
33
import type Monaco from 'monaco-editor';
44

55
import {
@@ -27,8 +27,6 @@ import type {
2727
} from './types';
2828
import {getEntitiesToFetchColumns} from './utils';
2929

30-
const prefixRegexp = /([^\s]+)$/;
31-
3230
function getCursorPosition(input: string, offset: number): CursorPosition {
3331
const lines = input.slice(0, offset).split('\n');
3432
// parser suggest to get cursor position where lineNumber and column starts from 1
@@ -38,19 +36,23 @@ function getCursorPosition(input: string, offset: number): CursorPosition {
3836
};
3937
}
4038

41-
function getCursorPrefix(input: string, offset: number) {
39+
const prefixRegexp = /([^\s]+)$/;
40+
41+
function getCursorPrefix(input: string, offset: number, re = prefixRegexp) {
4242
const prevText = input.slice(0, offset);
4343

44-
const match = prevText.match(prefixRegexp);
44+
const match = prevText.match(re);
4545
if (match && match[1]) {
4646
return match[1];
4747
}
4848
return '';
4949
}
5050

51+
const prefixToReplaceRegexp = /([^\\/\s`]+)$/;
52+
5153
function getRangeToInsertSuggestion(input: string, offset: number) {
5254
const cursorPosition = getCursorPosition(input, offset);
53-
const cursorPrefix = getCursorPrefix(input, offset);
55+
const cursorPrefix = getCursorPrefix(input, offset, prefixToReplaceRegexp);
5456
return {
5557
startColumn: cursorPosition.column - cursorPrefix.length,
5658
startLineNumber: cursorPosition.lineNumber,
@@ -59,6 +61,12 @@ function getRangeToInsertSuggestion(input: string, offset: number) {
5961
};
6062
}
6163

64+
/**
65+
* YQLAutocomplete class provides autocompletion suggestions for YQL queries.
66+
* It uses a set of constants to generate suggestions for various YQL elements.
67+
* The class is abstract and needs to be extended to provide implementation for
68+
* fetching entities and columns.
69+
*/
6270
export abstract class YQLAutocomplete {
6371
private readonly constants: AutocompleteConstants = {};
6472

@@ -82,21 +90,8 @@ export abstract class YQLAutocomplete {
8290
});
8391
}
8492

85-
removeBackticks(value: string) {
86-
let sliceStart = 0;
87-
let sliceEnd = value.length;
88-
if (value.startsWith('`')) {
89-
sliceStart = 1;
90-
}
91-
if (value.endsWith('`')) {
92-
sliceEnd = -1;
93-
}
94-
return value.slice(sliceStart, sliceEnd);
95-
}
96-
9793
async getSuggestions(input: string, offset: number) {
9894
const parseResult = await this.parseInput(input, offset);
99-
10095
let entitiesSuggestions: Monaco.languages.CompletionItem[] = [];
10196
let functionsSuggestions: Monaco.languages.CompletionItem[] = [];
10297
let aggregateFunctionsSuggestions: Monaco.languages.CompletionItem[] = [];
@@ -111,23 +106,24 @@ export abstract class YQLAutocomplete {
111106

112107
const rangeToInsertSuggestion = getRangeToInsertSuggestion(input, offset);
113108

109+
const cursorPrefix = getCursorPrefix(input, offset);
110+
114111
if (parseResult.suggestSimpleTypes) {
112+
const simpleTypes = await this.getSimpleTypes(cursorPrefix);
115113
simpleTypesSuggestions = await generateSimpleTypesSuggestion(
116114
rangeToInsertSuggestion,
117-
this.constants.types,
115+
simpleTypes,
118116
);
119117
}
120118
if (parseResult.suggestEntity) {
121-
const entityNamePrefix = getCursorPrefix(input, offset);
122-
123119
const fetchedEntities = await this.fetchEntities(
124-
entityNamePrefix,
120+
cursorPrefix,
125121
parseResult.suggestEntity,
126122
);
127123
entitiesSuggestions = await generateEntitiesSuggestion(
128124
rangeToInsertSuggestion,
129125
fetchedEntities,
130-
entityNamePrefix,
126+
cursorPrefix,
131127
);
132128
}
133129
if (parseResult.suggestVariables) {
@@ -138,41 +134,41 @@ export abstract class YQLAutocomplete {
138134
}
139135

140136
if (parseResult.suggestFunctions) {
137+
const simpleFunctions = await this.getSimpleFunctions(cursorPrefix);
141138
functionsSuggestions = await generateSimpleFunctionsSuggestion(
142139
rangeToInsertSuggestion,
143-
this.constants.simpleFunctions,
140+
simpleFunctions,
144141
);
145142
}
146143
if (parseResult.suggestAggregateFunctions) {
144+
const aggregateFunctions = await this.getAggregateFunctions(cursorPrefix);
147145
aggregateFunctionsSuggestions = await generateAggregateFunctionsSuggestion(
148146
rangeToInsertSuggestion,
149-
this.constants.aggregateFunctions,
147+
aggregateFunctions,
150148
);
151149
}
152150
if (parseResult.suggestWindowFunctions) {
151+
const windowFunctions = await this.getWindowFunctions(cursorPrefix);
153152
windowFunctionsSuggestions = await generateWindowFunctionsSuggestion(
154153
rangeToInsertSuggestion,
155-
this.constants.windowFunctions,
154+
windowFunctions,
156155
);
157156
}
158157
if (parseResult.suggestTableFunctions) {
158+
const tableFunctions = await this.getTableFunctions(cursorPrefix);
159159
tableFunctionsSuggestions = await generateTableFunctionsSuggestion(
160160
rangeToInsertSuggestion,
161-
this.constants.tableFunctions,
161+
tableFunctions,
162162
);
163163
}
164164

165165
if (parseResult.suggestUdfs) {
166-
udfsSuggestions = await generateUdfSuggestion(
167-
rangeToInsertSuggestion,
168-
this.constants.udfs,
169-
);
166+
const udfs = await this.getUdfs(cursorPrefix);
167+
udfsSuggestions = await generateUdfSuggestion(rangeToInsertSuggestion, udfs);
170168
}
171169
if (parseResult.suggestPragmas) {
172-
pragmasSuggestions = await generatePragmasSuggestion(
173-
rangeToInsertSuggestion,
174-
this.constants.pragmas,
175-
);
170+
const pragmas = await this.getPragmas(cursorPrefix);
171+
pragmasSuggestions = await generatePragmasSuggestion(rangeToInsertSuggestion, pragmas);
176172
}
177173
if (parseResult.suggestEntitySettings) {
178174
const entitySettings = await this.getEntitySettings(parseResult.suggestEntitySettings);
@@ -193,19 +189,12 @@ export abstract class YQLAutocomplete {
193189
);
194190

195191
if (parseResult.suggestColumns) {
196-
const normalizedSuggestions: YqlAutocompleteResult['suggestColumns'] = {
197-
...parseResult.suggestColumns,
198-
tables: parseResult.suggestColumns.tables?.map((table) => ({
199-
...table,
200-
name: this.normalizeTableName(table.name),
201-
})),
202-
};
203-
const entities = getEntitiesToFetchColumns(normalizedSuggestions);
192+
const entities = getEntitiesToFetchColumns(parseResult.suggestColumns);
204193
const fetchedColumns = await this.fetchEntityColumns(entities);
205194

206195
columnsSuggestions = await generateColumnsSuggestion(
207196
rangeToInsertSuggestion,
208-
normalizedSuggestions,
197+
parseResult.suggestColumns,
209198
parseResult.suggestVariables,
210199
fetchedColumns,
211200
);
@@ -233,24 +222,65 @@ export abstract class YQLAutocomplete {
233222
async parseInput(input: string, offset: number) {
234223
const cursorPosition = getCursorPosition(input, offset);
235224

236-
const {parseYqlQuery} = await import('@gravity-ui/websql-autocomplete/yql');
225+
const parseQuery = await this.getQueryParser();
237226
const cursorForParsing: BaseCursorPosition = {
238227
line: cursorPosition.lineNumber,
239228
column: cursorPosition.column,
240229
};
241-
return parseYqlQuery(input, cursorForParsing);
230+
return parseQuery(input, cursorForParsing);
242231
}
243232

244-
abstract getEntitySettings(entityType: YQLEntity): string[] | Promise<string[]>;
233+
async getQueryParser() {
234+
const {parseYqlQuery: parseQuery} = await import('@gravity-ui/websql-autocomplete/yql');
235+
return parseQuery;
236+
}
237+
238+
async getSimpleTypes(_prefix?: string) {
239+
return this.constants.types;
240+
}
241+
242+
async getUdfs(_prefix?: string) {
243+
return this.constants.udfs;
244+
}
245+
246+
async getPragmas(_prefix?: string) {
247+
return this.constants.pragmas;
248+
}
249+
async getWindowFunctions(_prefix?: string) {
250+
return this.constants.windowFunctions;
251+
}
245252

253+
async getTableFunctions(_prefix?: string) {
254+
return this.constants.tableFunctions;
255+
}
256+
async getAggregateFunctions(_prefix?: string) {
257+
return this.constants.aggregateFunctions;
258+
}
259+
260+
async getSimpleFunctions(_prefix?: string) {
261+
return this.constants.simpleFunctions;
262+
}
263+
264+
/**
265+
* Fetches entity settings based on provided entity type.
266+
* @param entityType
267+
* @returns A promise that resolves to an array of entity settings.
268+
*/
269+
abstract getEntitySettings(entityType: YQLEntity): Promise<string[]>;
270+
/**
271+
* Fetches entities based on the provided prefix and needed entity types.
272+
* @param prefix - The prefix to filter entities.
273+
* @param neededEntityTypes - An array of entity types to fetch.
274+
* @returns A promise that resolves to an array of fetched entities.
275+
*/
246276
abstract fetchEntities(
247277
prefix: string,
248278
neededEntityTypes: YQLEntity[],
249279
): Promise<FetchedEntity[]>;
250-
280+
/**
281+
* Fetches columns for the specified entities.
282+
* @param entityNames - An array of entity names to fetch columns for.
283+
* @returns A promise that resolves to an array of fetched columns.
284+
*/
251285
abstract fetchEntityColumns(entityNames: string[]): Promise<FetchedColumn[]>;
252-
253-
normalizeTableName(value: string) {
254-
return this.removeBackticks(value);
255-
}
256286
}

0 commit comments

Comments
 (0)