Skip to content

Commit

Permalink
feat(YQL): add logic to generate suggestions for yql
Browse files Browse the repository at this point in the history
  • Loading branch information
Raubzeug committed Feb 4, 2025
1 parent 43cc8b6 commit 5bbfc39
Show file tree
Hide file tree
Showing 8 changed files with 2,532 additions and 1,909 deletions.
3,692 changes: 1,790 additions & 1,902 deletions package-lock.json

Large diffs are not rendered by default.

14 changes: 8 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,20 @@
"url": "git+ssh://[email protected]/ydb-platform/monaco-yql-languages.git"
},
"devDependencies": {
"@gravity-ui/eslint-config": "^3.1.1",
"@gravity-ui/eslint-config": "^3.3.0",
"@gravity-ui/prettier-config": "^1.1.0",
"@gravity-ui/stylelint-config": "^4.0.1",
"@gravity-ui/tsconfig": "^1.0.0",
"eslint": "^8.10.0",
"husky": "^9.0.11",
"monaco-editor": "^0.32.1",
"prettier": "^3.2.5",
"@gravity-ui/websql-autocomplete": "^13.7.0",
"eslint": "^8.57.1",
"husky": "^9.1.7",
"monaco-editor": "^0.52.2",
"prettier": "^3.4.2",
"rimraf": "^3.0.2",
"typescript": "^5.4.2"
"typescript": "^5.7.3"
},
"peerDependencies": {
"@gravity-ui/websql-autocomplete": "^13.7.0",
"monaco-editor": ">=0.27.0"
},
"nano-staged": {
Expand Down
330 changes: 330 additions & 0 deletions src/yql/autocomplete/generateSuggestions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,330 @@
import type {
ColumnAliasSuggestion,
KeywordSuggestion,
VariableSuggestion,
} from '@gravity-ui/websql-autocomplete/shared';
import type {YqlAutocompleteResult} from '@gravity-ui/websql-autocomplete/yql';
import * as monaco from '../../fillers/monaco-editor-core';

import type {FetchedColumn, FetchedEntity} from './types';
import {
getSuggestionIndex,
isVariable,
removeBackticks,
removeStringDuplicates,
suggestionIndexToWeight,
wrapStringToBackticks,
} from './utils';

export async function generateSimpleTypesSuggestion(
rangeToInsertSuggestion: monaco.IRange,
simpleTypes: string[] = [],
): Promise<monaco.languages.CompletionItem[]> {
return simpleTypes.map((el) => ({
label: el,
insertText: el,
kind: monaco.languages.CompletionItemKind.TypeParameter,
detail: 'Type',
range: rangeToInsertSuggestion,
sortText: suggestionIndexToWeight(getSuggestionIndex('suggestSimpleTypes')),
}));
}

export async function generateEntitiesSuggestion(
rangeToInsertSuggestion: monaco.IRange,
fetchedEntities: FetchedEntity[],
prefix = '',
): Promise<monaco.languages.CompletionItem[]> {
const withBackticks = prefix?.startsWith('`');

return fetchedEntities.reduce<monaco.languages.CompletionItem[]>(
(acc, {value, detail, isDir}) => {
const label = isDir ? `${value}/` : value;
let labelAsSnippet;
if (isDir && !withBackticks) {
labelAsSnippet = `\`${label}$0\``;
}
const suggestionIndex = acc.length;
acc.push({
label,
insertText: labelAsSnippet ?? label,
kind: isDir
? monaco.languages.CompletionItemKind.Folder
: monaco.languages.CompletionItemKind.Text,
insertTextRules: labelAsSnippet
? monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet
: monaco.languages.CompletionItemInsertTextRule.None,
detail: detail,
range: rangeToInsertSuggestion,
command: label.endsWith('/')
? {id: 'editor.action.triggerSuggest', title: ''}
: undefined,
// first argument is responsible for sorting groups of suggestions, the second - to preserve suggestions order returned from backend
sortText:
suggestionIndexToWeight(getSuggestionIndex('suggestEntity')) +
suggestionIndexToWeight(suggestionIndex),
});
return acc;
},
[],
);
}

export function generateKeywordsSuggestion(
rangeToInsertSuggestion: monaco.IRange,
suggestKeywords: KeywordSuggestion[] = [],
) {
return suggestKeywords.map((keywordSuggestion) => ({
label: keywordSuggestion.value,
insertText: keywordSuggestion.value,
kind: monaco.languages.CompletionItemKind.Keyword,
detail: 'Keyword',
range: rangeToInsertSuggestion,
sortText: suggestionIndexToWeight(getSuggestionIndex('suggestKeywords')),
}));
}

export function generateVariableSuggestion(
rangeToInsertSuggestion: monaco.IRange,
suggestVariables: VariableSuggestion[] = [],
) {
return suggestVariables.map(({name}) => {
const variable = '$' + name;
return {
label: variable,
insertText: variable,
kind: monaco.languages.CompletionItemKind.Variable,
detail: 'Variable',
range: rangeToInsertSuggestion,
sortText: suggestionIndexToWeight(getSuggestionIndex('suggestVariables')),
};
});
}

export async function generateSimpleFunctionsSuggestion(
rangeToInsertSuggestion: monaco.IRange,
functions: string[] = [],
): Promise<monaco.languages.CompletionItem[]> {
return functions.map((el) => ({
label: el,
insertText: el,
kind: monaco.languages.CompletionItemKind.Function,
detail: 'Function',
range: rangeToInsertSuggestion,
sortText: suggestionIndexToWeight(getSuggestionIndex('suggestFunctions')),
}));
}

export async function generateAggregateFunctionsSuggestion(
rangeToInsertSuggestion: monaco.IRange,
aggreagteFunctions: string[] = [],
): Promise<monaco.languages.CompletionItem[]> {
return aggreagteFunctions.map((el) => ({
label: el,
insertText: el,
kind: monaco.languages.CompletionItemKind.Function,
detail: 'Aggregate function',
range: rangeToInsertSuggestion,
sortText: suggestionIndexToWeight(getSuggestionIndex('suggestAggregateFunctions')),
}));
}

export async function generateWindowFunctionsSuggestion(
rangeToInsertSuggestion: monaco.IRange,
windowFunctions: string[] = [],
): Promise<monaco.languages.CompletionItem[]> {
return windowFunctions.map((el) => ({
label: el,
insertText: el,
kind: monaco.languages.CompletionItemKind.Function,
detail: 'Window function',
range: rangeToInsertSuggestion,
sortText: suggestionIndexToWeight(getSuggestionIndex('suggestWindowFunctions')),
}));
}

export async function generateTableFunctionsSuggestion(
rangeToInsertSuggestion: monaco.IRange,
tableFunctions: string[] = [],
): Promise<monaco.languages.CompletionItem[]> {
return tableFunctions.map((el) => ({
label: el,
insertText: el,
kind: monaco.languages.CompletionItemKind.Function,
detail: 'Table function',
range: rangeToInsertSuggestion,
sortText: suggestionIndexToWeight(getSuggestionIndex('suggestTableFunctions')),
}));
}

export async function generateUdfSuggestion(
rangeToInsertSuggestion: monaco.IRange,
udfs: string[] = [],
): Promise<monaco.languages.CompletionItem[]> {
return udfs.map((el) => ({
label: el,
insertText: el,
kind: monaco.languages.CompletionItemKind.Function,
detail: 'UDF',
range: rangeToInsertSuggestion,
sortText: suggestionIndexToWeight(getSuggestionIndex('suggestUdfs')),
}));
}

export async function generatePragmasSuggestion(
rangeToInsertSuggestion: monaco.IRange,
pragmas: string[] = [],
): Promise<monaco.languages.CompletionItem[]> {
return pragmas.map((el) => ({
label: el,
insertText: el,
kind: monaco.languages.CompletionItemKind.Module,
detail: 'Pragma',
range: rangeToInsertSuggestion,
sortText: suggestionIndexToWeight(getSuggestionIndex('suggestPragmas')),
}));
}

export async function generateEntitySettingsSuggestion(
rangeToInsertSuggestion: monaco.IRange,
entitySettings: string[] = [],
): Promise<monaco.languages.CompletionItem[]> {
return entitySettings.map((el) => ({
label: el,
insertText: el,
kind: monaco.languages.CompletionItemKind.Property,
detail: 'Setting',
range: rangeToInsertSuggestion,
sortText: suggestionIndexToWeight(getSuggestionIndex('suggestEntitySettings')),
}));
}

export function generateColumnAliasesSuggestion(
rangeToInsertSuggestion: monaco.IRange,
suggestColumnAliases: ColumnAliasSuggestion[] = [],
) {
return suggestColumnAliases.map((columnAliasSuggestion) => ({
label: columnAliasSuggestion.name,
insertText: columnAliasSuggestion.name,
kind: monaco.languages.CompletionItemKind.Variable,
detail: 'Column alias',
range: rangeToInsertSuggestion,
sortText: suggestionIndexToWeight(getSuggestionIndex('suggestColumnAliases')),
}));
}

export async function generateColumnsSuggestion(
rangeToInsertSuggestion: monaco.IRange,
suggestColumns: YqlAutocompleteResult['suggestColumns'],
suggestVariables: YqlAutocompleteResult['suggestVariables'],
fetchedColumns: FetchedColumn[],
): Promise<monaco.languages.CompletionItem[]> {
if (!suggestColumns?.tables) {
return [];
}
const suggestions: monaco.languages.CompletionItem[] = [];
const allColumnsSuggestion = suggestColumns.all ? ([] as string[]) : undefined;
const multi = suggestColumns.tables.length > 1;

const tableNames = suggestColumns.tables.map((entity) => entity.name);
const filteredTableNames = removeStringDuplicates(tableNames);

const variableSources = filteredTableNames.filter(isVariable);

const columnsFromVariable: FetchedColumn[] = [];
if (variableSources.length) {
variableSources.forEach((source) => {
const newColumns =
suggestVariables
// Variable name from suggestions doesn't include $ sign
?.find((variable) => source.slice(1) === variable.name)
?.value?.columns?.map((col) => ({
name: col,
parent: source,
})) ?? [];
columnsFromVariable.push(...newColumns);
});
}

const predefinedColumns: FetchedColumn[] = suggestColumns.tables.reduce<FetchedColumn[]>(
(acc, entity) => {
const columns = entity.columns;
if (columns) {
acc.push(
...columns.map((col) => ({
name: col,
parent: entity.name,
})),
);
}
return acc;
},
[],
);

const tableNameToAliasMap = suggestColumns.tables?.reduce(
(acc, entity) => {
const name = removeBackticks(entity.name);
const aliases = acc[name] ?? [];
if (entity.alias) {
aliases.push(entity.alias);
}
acc[name] = aliases;
return acc;
},
{} as Record<string, string[]>,
);

[...fetchedColumns, ...columnsFromVariable, ...predefinedColumns].forEach((col) => {
const normalizedName = wrapStringToBackticks(col.name);

const aliases = tableNameToAliasMap[removeBackticks(col.parent)];
const currentSuggestionIndex = suggestions.length;
if (aliases?.length) {
aliases.forEach((a) => {
const columnNameSuggestion = `${a}.${normalizedName}`;
suggestions.push({
label: {label: columnNameSuggestion, description: col.detail},
insertText: columnNameSuggestion,
kind: monaco.languages.CompletionItemKind.Variable,
detail: 'Column',
range: rangeToInsertSuggestion,
sortText:
suggestionIndexToWeight(getSuggestionIndex('suggestColumns')) +
suggestionIndexToWeight(currentSuggestionIndex),
});
allColumnsSuggestion?.push(columnNameSuggestion);
});
} else {
let columnNameSuggestion = normalizedName;
if (multi) {
columnNameSuggestion = `${wrapStringToBackticks(col.parent)}.${normalizedName}`;
}
suggestions.push({
label: {
label: columnNameSuggestion,
description: col.detail,
},
insertText: columnNameSuggestion,
kind: monaco.languages.CompletionItemKind.Variable,
detail: 'Column',
range: rangeToInsertSuggestion,
sortText:
suggestionIndexToWeight(getSuggestionIndex('suggestColumns')) +
suggestionIndexToWeight(currentSuggestionIndex),
});
allColumnsSuggestion?.push(columnNameSuggestion);
}
});
if (allColumnsSuggestion && allColumnsSuggestion.length > 1) {
const allColumns = allColumnsSuggestion.join(', ');
suggestions.push({
label: allColumns,
insertText: allColumns,
kind: monaco.languages.CompletionItemKind.Variable,
range: rangeToInsertSuggestion,
sortText: suggestionIndexToWeight(getSuggestionIndex('suggestAllColumns')),
});
}
return suggestions;
}
2 changes: 2 additions & 0 deletions src/yql/autocomplete/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export {YQLAutocomplete} from './yqlAutocomplete';
export * from './types';
29 changes: 29 additions & 0 deletions src/yql/autocomplete/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type {YQLEntity} from '@gravity-ui/websql-autocomplete/yql';

export type CursorPosition = {lineNumber: number; column: number};

export type AutocompleteEntityType = YQLEntity | 'directory';

export type FetchedEntity = {
value: string;
isDir: boolean;
detail?: string;
};

export type FetchedColumn = {name: string; detail?: string; parent: string};

type ConstantsGetter = () => string[] | Promise<string[]>;

export type AutocompleteConstant =
| 'types'
| 'windowFunctions'
| 'udfs'
| 'pragmas'
| 'simpleFunctions'
| 'tableFunctions'
| 'aggregateFunctions';

export type AutocompleteConstantsInit = Partial<
Record<AutocompleteConstant, string[] | ConstantsGetter>
>;
export type AutocompleteConstants = Partial<Record<AutocompleteConstant, string[]>>;
Loading

0 comments on commit 5bbfc39

Please sign in to comment.