diff --git a/package-lock.json b/package-lock.json index c6745cd864..ff7a1f7618 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "@gravity-ui/react-data-table": "^2.1.1", "@gravity-ui/table": "^1.7.0", "@gravity-ui/uikit": "^6.40.0", - "@gravity-ui/websql-autocomplete": "^13.4.0", + "@gravity-ui/websql-autocomplete": "^13.7.0", "@hookform/resolvers": "^3.10.0", "@reduxjs/toolkit": "^2.5.0", "@tanstack/react-table": "^8.20.6", @@ -83,7 +83,7 @@ "lint-staged": "^15.3.0", "mini-css-extract-plugin": "^2.9.2", "monaco-editor-webpack-plugin": "^7.1.0", - "monaco-yql-languages": "^1.2.1", + "monaco-yql-languages": "^1.3.0", "npm-run-all": "^4.1.5", "postcss": "^8.5.1", "prettier": "^3.4.2", @@ -3275,9 +3275,10 @@ } }, "node_modules/@gravity-ui/websql-autocomplete": { - "version": "13.4.0", - "resolved": "https://registry.npmjs.org/@gravity-ui/websql-autocomplete/-/websql-autocomplete-13.4.0.tgz", - "integrity": "sha512-cv4lmCh4C2Ubli9zw4Ivptl5WGn5uDX/R513eSHbOLKnt9rfwHtlsJxUOK6Qt2BbNHg0/3Km1vyEcmJerta6dg==", + "version": "13.7.0", + "resolved": "https://registry.npmjs.org/@gravity-ui/websql-autocomplete/-/websql-autocomplete-13.7.0.tgz", + "integrity": "sha512-94lEiBz60MklVkhZKRUGf8dpt79M+sGKau++TBq+mj3j1owSJk39c3ic6Kwk2X04R+A5TREoaw0aKIwdnskSHQ==", + "license": "Apache-2.0", "dependencies": { "antlr4-c3": "^3.4.1", "antlr4ng": "^3.0.4", @@ -18529,11 +18530,13 @@ } }, "node_modules/monaco-yql-languages": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/monaco-yql-languages/-/monaco-yql-languages-1.2.1.tgz", - "integrity": "sha512-mIV1i8jvpLm6IF89gHuN81x6a0UY8gXAYbfT7d7267TD9mAaxvHrFsFMTK0yz7e5vHtZ3nz34FhPDSbwAEMCvg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/monaco-yql-languages/-/monaco-yql-languages-1.3.0.tgz", + "integrity": "sha512-E7pQV6rqXscOnomRRM7wkQKD4nCqsq2rNHrqh58mNbuxpJAO8Zs0ITPlKsSIbHe6olqn8Y+7mSc+E9DKi9gJvg==", "dev": true, + "license": "MIT", "peerDependencies": { + "@gravity-ui/websql-autocomplete": "^13.7.0", "monaco-editor": ">=0.27.0" } }, diff --git a/package.json b/package.json index 4a3c0ea64c..2865c6aea7 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "@gravity-ui/react-data-table": "^2.1.1", "@gravity-ui/table": "^1.7.0", "@gravity-ui/uikit": "^6.40.0", - "@gravity-ui/websql-autocomplete": "^13.4.0", + "@gravity-ui/websql-autocomplete": "^13.7.0", "@hookform/resolvers": "^3.10.0", "@reduxjs/toolkit": "^2.5.0", "@tanstack/react-table": "^8.20.6", @@ -146,7 +146,7 @@ "lint-staged": "^15.3.0", "mini-css-extract-plugin": "^2.9.2", "monaco-editor-webpack-plugin": "^7.1.0", - "monaco-yql-languages": "^1.2.1", + "monaco-yql-languages": "^1.3.0", "npm-run-all": "^4.1.5", "postcss": "^8.5.1", "prettier": "^3.4.2", diff --git a/src/utils/monaco/yql/generateSuggestions.ts b/src/utils/monaco/yql/generateSuggestions.ts index 4f06df1256..7624cd6f10 100644 --- a/src/utils/monaco/yql/generateSuggestions.ts +++ b/src/utils/monaco/yql/generateSuggestions.ts @@ -1,12 +1,5 @@ -import type { - ColumnAliasSuggestion, - KeywordSuggestion, - VariableSuggestion, -} from '@gravity-ui/websql-autocomplete/shared'; -import type {YQLEntity, YqlAutocompleteResult} from '@gravity-ui/websql-autocomplete/yql'; -import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; +import type {YQLEntity} from '@gravity-ui/websql-autocomplete/yql'; -import {isAutocompleteColumn} from '../../../types/api/autocomplete'; import type { AutocompleteColumn, AutocompleteEntityType, @@ -24,41 +17,6 @@ import { WindowFunctions, } from './constants'; -const CompletionItemKind: { - [K in keyof typeof monaco.languages.CompletionItemKind]: (typeof monaco.languages.CompletionItemKind)[K]; -} = { - Method: 0, - Function: 1, - Constructor: 2, - Field: 3, - Variable: 4, - Class: 5, - Struct: 6, - Interface: 7, - Module: 8, - Property: 9, - Event: 10, - Operator: 11, - Unit: 12, - Value: 13, - Constant: 14, - Enum: 15, - EnumMember: 16, - Keyword: 17, - Text: 18, - Color: 19, - File: 20, - Reference: 21, - Customcolor: 22, - Folder: 23, - TypeParameter: 24, - User: 25, - Issue: 26, - Snippet: 27, -}; - -const re = /[\s'"-/@]/; - const suggestionEntityToAutocomplete: Partial> = { externalDataSource: ['external_data_source'], externalTable: ['external_table'], @@ -74,7 +32,11 @@ const commonSuggestionEntities: AutocompleteEntityType[] = ['dir', 'unknown', 'e const directoryTypes: AutocompleteEntityType[] = ['dir', 'ext_sub_domain']; -function filterAutocompleteEntities( +export function checkIsDirectory(type: AutocompleteEntityType) { + return directoryTypes.includes(type); +} + +export function filterAutocompleteEntities( autocompleteEntities: TAutocompleteEntity[] | undefined, suggestions: YQLEntity[], ) { @@ -88,15 +50,7 @@ function filterAutocompleteEntities( return autocompleteEntities?.filter(({Type}) => suggestionsSet.has(Type)); } -function wrapStringToBackticks(value: string) { - let result = value; - if (value.match(re)) { - result = `\`${value}\``; - } - return result; -} - -function removeBackticks(value: string) { +export function removeBackticks(value: string) { let sliceStart = 0; let sliceEnd = value.length; if (value.startsWith('`')) { @@ -108,10 +62,6 @@ function removeBackticks(value: string) { return value.slice(sliceStart, sliceEnd); } -function isVariable(value: string) { - return value.startsWith('$'); -} - function removeStartSlash(value: string) { if (value.startsWith('/')) { return value.slice(1); @@ -119,7 +69,7 @@ function removeStartSlash(value: string) { return value; } -function normalizeEntityPrefix(value = '', database: string) { +export function normalizeEntityPrefix(value = '', database: string) { const valueWithoutBackticks = removeBackticks(value); if (!valueWithoutBackticks.startsWith('/')) { return valueWithoutBackticks; @@ -132,60 +82,32 @@ function normalizeEntityPrefix(value = '', database: string) { return removeStartSlash(cleanedValue); } -type SuggestionType = - | keyof Omit - | 'suggestAllColumns'; - -const SuggestionsWeight: Record = { - suggestTemplates: 0, - suggestPragmas: 1, - suggestEntity: 2, - suggestAllColumns: 3, - suggestColumns: 4, - suggestColumnAliases: 5, - suggestVariables: 6, - suggestTableIndexes: 7, - suggestTableHints: 8, - suggestEntitySettings: 9, - suggestKeywords: 10, - suggestAggregateFunctions: 11, - suggestTableFunctions: 12, - suggestWindowFunctions: 13, - suggestFunctions: 14, - suggestSimpleTypes: 15, - suggestUdfs: 16, -}; - -function getSuggestionIndex(suggestionType: SuggestionType) { - return SuggestionsWeight[suggestionType]; -} - -async function getSimpleFunctions() { +export async function getSimpleFunctions() { return SimpleFunctions; } -async function getWindowFunctions() { +export async function getWindowFunctions() { return WindowFunctions; } -async function getTableFunctions() { +export async function getTableFunctions() { return TableFunction; } -async function getAggregateFunctions() { +export async function getAggregateFunctions() { return AggregateFunctions; } -async function getPragmas() { +export async function getPragmas() { return Pragmas; } -async function getEntitySettings(entityType: YQLEntity) { +export async function getEntitySettings(entityType: YQLEntity) { return EntitySettings[entityType]; } -async function getUdfs() { +export async function getUdfs() { return Udfs; } -async function getSimpleTypes() { +export async function getSimpleTypes() { return SimpleTypes; } -function getColumnDetails(col: AutocompleteColumn) { +export function getColumnDetails(col: AutocompleteColumn) { const {PKIndex, NotNull, Default} = col; const details = []; if (PKIndex !== undefined) { @@ -202,375 +124,14 @@ function getColumnDetails(col: AutocompleteColumn) { return detailsString; } -export async function generateColumnsSuggestion( - rangeToInsertSuggestion: monaco.IRange, - suggestColumns: YqlAutocompleteResult['suggestColumns'], - suggestVariables: YqlAutocompleteResult['suggestVariables'], - database: string, -): Promise { - if (!suggestColumns?.tables) { - return []; - } - const suggestions: monaco.languages.CompletionItem[] = []; - const normalizedColumns = suggestColumns.all ? ([] as string[]) : undefined; - const multi = suggestColumns.tables.length > 1; - - const normalizedSuggestions = - suggestColumns.tables?.map((entity) => { - let normalizedEntityName = removeBackticks(entity.name); - if (!normalizedEntityName.endsWith('/') && !isVariable(normalizedEntityName)) { +export function normalizeEntityNames(entities: string[], database: string) { + return ( + entities.map((entity) => { + let normalizedEntityName = removeBackticks(entity); + if (!normalizedEntityName.endsWith('/')) { normalizedEntityName = `${normalizedEntityName}/`; } - return {...entity, name: normalizeEntityPrefix(normalizedEntityName, database)}; - }) ?? []; - - const normalizedTableNames = normalizedSuggestions.map((entity) => entity.name); - - // remove duplicates if any - const filteredTableNames = Array.from(new Set(normalizedTableNames)); - - const tableSources = filteredTableNames.filter((name) => !isVariable(name)); - - let autocompleteEntities: TAutocompleteEntity[] = []; - if (tableSources.length) { - const autocompleteResponse = await window.api.viewer.autocomplete({ - database, - table: tableSources, - limit: 1000, - }); - if (autocompleteResponse.Success) { - autocompleteEntities = autocompleteResponse.Result.Entities ?? []; - } - } - - const variableSources = filteredTableNames.filter(isVariable); - const columnsFromVariable: TAutocompleteEntity[] = []; - 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, - Type: 'column' as const, - Parent: source, - })) ?? []; - columnsFromVariable.push(...newColumns); - }); - } - - const predefinedColumns: TAutocompleteEntity[] = normalizedSuggestions.reduce< - TAutocompleteEntity[] - >((acc, entity) => { - const columns = entity.columns; - if (columns) { - acc.push( - ...columns.map((col) => ({ - Name: col, - Type: 'column' as const, - Parent: entity.name, - })), - ); - } - return acc; - }, []); - - const tableNameToAliasMap = suggestColumns.tables?.reduce( - (acc, entity) => { - const normalizedEntityName = normalizeEntityPrefix( - removeBackticks(entity.name), - database, - ); - const aliases = acc[normalizedEntityName] ?? []; - if (entity.alias) { - aliases.push(entity.alias); - } - acc[normalizedEntityName] = aliases; - return acc; - }, - {} as Record, + return normalizeEntityPrefix(normalizedEntityName, database); + }) ?? [] ); - - [...autocompleteEntities, ...columnsFromVariable, ...predefinedColumns].forEach((col) => { - if (!isAutocompleteColumn(col)) { - return; - } - - const columnDetails = getColumnDetails(col); - const normalizedName = wrapStringToBackticks(col.Name); - - const normalizedParentName = normalizeEntityPrefix(col.Parent, database); - const aliases = tableNameToAliasMap[normalizedParentName]; - const currentSuggestionIndex = suggestions.length; - if (aliases?.length) { - aliases.forEach((a) => { - const columnNameSuggestion = `${a}.${normalizedName}`; - suggestions.push({ - label: {label: columnNameSuggestion, description: columnDetails}, - insertText: columnNameSuggestion, - kind: CompletionItemKind.Variable, - detail: 'Column', - range: rangeToInsertSuggestion, - sortText: - suggestionIndexToWeight(getSuggestionIndex('suggestColumns')) + - suggestionIndexToWeight(currentSuggestionIndex), - }); - normalizedColumns?.push(columnNameSuggestion); - }); - } else { - let columnNameSuggestion = normalizedName; - if (multi) { - columnNameSuggestion = `${wrapStringToBackticks(normalizedParentName)}.${normalizedName}`; - } - suggestions.push({ - label: { - label: columnNameSuggestion, - description: columnDetails, - }, - insertText: columnNameSuggestion, - kind: CompletionItemKind.Variable, - detail: 'Column', - range: rangeToInsertSuggestion, - sortText: - suggestionIndexToWeight(getSuggestionIndex('suggestColumns')) + - suggestionIndexToWeight(currentSuggestionIndex), - }); - normalizedColumns?.push(columnNameSuggestion); - } - }); - if (normalizedColumns && normalizedColumns.length > 1) { - const allColumns = normalizedColumns.join(', '); - suggestions.push({ - label: allColumns, - insertText: allColumns, - kind: CompletionItemKind.Variable, - range: rangeToInsertSuggestion, - sortText: suggestionIndexToWeight(getSuggestionIndex('suggestAllColumns')), - }); - } - return suggestions; -} - -export function generateColumnAliasesSuggestion( - rangeToInsertSuggestion: monaco.IRange, - suggestColumnAliases?: ColumnAliasSuggestion[], -) { - if (!suggestColumnAliases) { - return []; - } - return suggestColumnAliases?.map((columnAliasSuggestion) => ({ - label: columnAliasSuggestion.name, - insertText: columnAliasSuggestion.name, - kind: CompletionItemKind.Variable, - detail: 'Column alias', - range: rangeToInsertSuggestion, - sortText: suggestionIndexToWeight(getSuggestionIndex('suggestColumnAliases')), - })); -} -export function generateKeywordsSuggestion( - rangeToInsertSuggestion: monaco.IRange, - suggestKeywords?: KeywordSuggestion[], -) { - if (!suggestKeywords) { - return []; - } - return suggestKeywords.map((keywordSuggestion) => ({ - label: keywordSuggestion.value, - insertText: keywordSuggestion.value, - kind: CompletionItemKind.Keyword, - detail: 'Keyword', - range: rangeToInsertSuggestion, - sortText: suggestionIndexToWeight(getSuggestionIndex('suggestKeywords')), - })); -} - -export function generateVariableSuggestion( - rangeToInsertSuggestion: monaco.IRange, - suggestVariables?: VariableSuggestion[], -) { - if (!suggestVariables) { - return []; - } - return suggestVariables.map(({name}) => { - const variable = '$' + name; - return { - label: variable, - insertText: variable, - kind: CompletionItemKind.Variable, - detail: 'Variable', - range: rangeToInsertSuggestion, - sortText: suggestionIndexToWeight(getSuggestionIndex('suggestVariables')), - }; - }); -} - -export async function generateEntitiesSuggestion( - rangeToInsertSuggestion: monaco.IRange, - suggestEntities: YQLEntity[], - database: string, - prefix?: string, -): Promise { - const normalizedPrefix = normalizeEntityPrefix(prefix, database); - const data = await window.api.viewer.autocomplete({ - database, - prefix: normalizedPrefix, - limit: 1000, - }); - const withBackticks = prefix?.startsWith('`'); - if (data.Success) { - const filteredEntities = filterAutocompleteEntities(data.Result.Entities, suggestEntities); - if (!filteredEntities) { - return []; - } - return filteredEntities.reduce((acc, {Name, Type}) => { - const isDir = directoryTypes.includes(Type); - const label = isDir ? `${Name}/` : Name; - let labelAsSnippet; - if (isDir && !withBackticks) { - labelAsSnippet = `\`${label}$0\``; - } - const suggestionIndex = acc.length; - acc.push({ - label, - insertText: labelAsSnippet ?? label, - kind: isDir ? CompletionItemKind.Folder : CompletionItemKind.Text, - insertTextRules: labelAsSnippet - ? monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet - : monaco.languages.CompletionItemInsertTextRule.None, - detail: Type, - 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; - }, [] as monaco.languages.CompletionItem[]); - } - return []; -} -export async function generateSimpleFunctionsSuggestion( - rangeToInsertSuggestion: monaco.IRange, -): Promise { - const functions = await getSimpleFunctions(); - return functions.map((el) => ({ - label: el, - insertText: el, - kind: CompletionItemKind.Function, - detail: 'Function', - range: rangeToInsertSuggestion, - sortText: suggestionIndexToWeight(getSuggestionIndex('suggestFunctions')), - })); -} -export async function generateSimpleTypesSuggestion( - rangeToInsertSuggestion: monaco.IRange, -): Promise { - const simpleTypes = await getSimpleTypes(); - return simpleTypes.map((el) => ({ - label: el, - insertText: el, - kind: CompletionItemKind.TypeParameter, - detail: 'Type', - range: rangeToInsertSuggestion, - sortText: suggestionIndexToWeight(getSuggestionIndex('suggestSimpleTypes')), - })); -} -export async function generateUdfSuggestion( - rangeToInsertSuggestion: monaco.IRange, -): Promise { - const udfs = await getUdfs(); - return udfs.map((el) => ({ - label: el, - insertText: el, - kind: CompletionItemKind.Function, - detail: 'UDF', - range: rangeToInsertSuggestion, - sortText: suggestionIndexToWeight(getSuggestionIndex('suggestUdfs')), - })); -} -export async function generateWindowFunctionsSuggestion( - rangeToInsertSuggestion: monaco.IRange, -): Promise { - const windowFunctions = await getWindowFunctions(); - return windowFunctions.map((el) => ({ - label: el, - insertText: el, - kind: CompletionItemKind.Function, - detail: 'Window function', - range: rangeToInsertSuggestion, - sortText: suggestionIndexToWeight(getSuggestionIndex('suggestWindowFunctions')), - })); -} -export async function generateTableFunctionsSuggestion( - rangeToInsertSuggestion: monaco.IRange, -): Promise { - const tableFunctions = await getTableFunctions(); - return tableFunctions.map((el) => ({ - label: el, - insertText: el, - kind: CompletionItemKind.Function, - detail: 'Table function', - range: rangeToInsertSuggestion, - sortText: suggestionIndexToWeight(getSuggestionIndex('suggestTableFunctions')), - })); -} -export async function generateAggregateFunctionsSuggestion( - rangeToInsertSuggestion: monaco.IRange, -): Promise { - const aggreagteFunctions = await getAggregateFunctions(); - return aggreagteFunctions.map((el) => ({ - label: el, - insertText: el, - kind: CompletionItemKind.Function, - detail: 'Aggregate function', - range: rangeToInsertSuggestion, - sortText: suggestionIndexToWeight(getSuggestionIndex('suggestAggregateFunctions')), - })); -} -export async function generatePragmasSuggestion( - rangeToInsertSuggestion: monaco.IRange, -): Promise { - const pragmas = await getPragmas(); - return pragmas.map((el) => ({ - label: el, - insertText: el, - kind: CompletionItemKind.Module, - detail: 'Pragma', - range: rangeToInsertSuggestion, - sortText: suggestionIndexToWeight(getSuggestionIndex('suggestPragmas')), - })); -} -export async function generateEntitySettingsSuggestion( - rangeToInsertSuggestion: monaco.IRange, - entityType: YQLEntity, -): Promise { - const entitySettings = await getEntitySettings(entityType); - return entitySettings.map((el) => ({ - label: el, - insertText: el, - kind: CompletionItemKind.Property, - detail: 'Setting', - range: rangeToInsertSuggestion, - sortText: suggestionIndexToWeight(getSuggestionIndex('suggestEntitySettings')), - })); -} - -const alphabet = 'abcdefghijklmnopqrstuvwxyz'; - -function suggestionIndexToWeight(index: number): string { - const characterInsideAlphabet = alphabet[index]; - if (characterInsideAlphabet) { - return characterInsideAlphabet; - } - - const duplicateTimes = Math.floor(index / alphabet.length); - const remains = index % alphabet.length; - - const lastCharacter = alphabet.slice(-1); - - return lastCharacter.repeat(duplicateTimes) + alphabet[remains]; } diff --git a/src/utils/monaco/yql/yql.completionItemProvider.ts b/src/utils/monaco/yql/yql.completionItemProvider.ts index b92097c9c2..7708de1172 100644 --- a/src/utils/monaco/yql/yql.completionItemProvider.ts +++ b/src/utils/monaco/yql/yql.completionItemProvider.ts @@ -1,20 +1,81 @@ -import * as monaco from 'monaco-editor'; +import type {YQLEntity} from '@gravity-ui/websql-autocomplete/yql'; +import type {FetchedColumn} from 'monaco-yql-languages/build/yql/autocomplete'; +import {registerCompletionItemProvider} from 'monaco-yql-languages/build/yql/autocomplete'; import {LANGUAGE_ID} from 'monaco-yql-languages/build/yql/yql.contribution'; -import {createProvideSuggestionsFunction} from './yqlSuggestions'; +import {isAutocompleteColumn} from '../../../types/api/autocomplete'; +import type {TAutocompleteEntity} from '../../../types/api/autocomplete'; -let completionProvider: monaco.IDisposable | undefined; - -function disableCodeSuggestions(): void { - if (completionProvider) { - completionProvider.dispose(); - } -} +import { + checkIsDirectory, + filterAutocompleteEntities, + getAggregateFunctions, + getColumnDetails, + getEntitySettings, + getPragmas, + getSimpleFunctions, + getSimpleTypes, + getTableFunctions, + getUdfs, + getWindowFunctions, + normalizeEntityNames, + removeBackticks, +} from './generateSuggestions'; export function registerYQLCompletionItemProvider(database: string) { - disableCodeSuggestions(); - completionProvider = monaco.languages.registerCompletionItemProvider(LANGUAGE_ID, { - triggerCharacters: [' ', '.', '`', '(', '/'], - provideCompletionItems: createProvideSuggestionsFunction(database), + const fetchEntities = async (prefix: string, neededTypes: YQLEntity[]) => { + const data = await window.api.viewer.autocomplete({ + database, + prefix: removeBackticks(prefix), + limit: 1000, + }); + if (!data.Success || !data.Result.Entities) { + return []; + } + const filteredEntities = filterAutocompleteEntities(data.Result.Entities, neededTypes); + return ( + filteredEntities?.map(({Name, Type}) => ({ + value: Name, + detail: Type, + isDir: checkIsDirectory(Type), + })) ?? [] + ); + }; + + const fetchEntityColumns = async (entityNames: string[]) => { + let autocompleteEntities: TAutocompleteEntity[] = []; + const normalizedNames = normalizeEntityNames(entityNames, database); + const autocompleteResponse = await window.api.viewer.autocomplete({ + database, + table: normalizedNames, + limit: 1000, + }); + if (autocompleteResponse.Success) { + autocompleteEntities = autocompleteResponse.Result.Entities ?? []; + } + const result: FetchedColumn[] = []; + autocompleteEntities.forEach((entity) => { + if (isAutocompleteColumn(entity)) { + result.push({ + name: entity.Name, + detail: getColumnDetails(entity), + parent: entity.Parent, + }); + } + }); + return result; + }; + + registerCompletionItemProvider(LANGUAGE_ID, [' ', '.', '`', '(', '/'], { + fetchEntities, + fetchEntityColumns, + getEntitySettings, + getPragmas, + getSimpleFunctions, + getAggregateFunctions, + getTableFunctions, + getWindowFunctions, + getUdfs, + getSimpleTypes, }); } diff --git a/src/utils/monaco/yql/yqlSuggestions.ts b/src/utils/monaco/yql/yqlSuggestions.ts deleted file mode 100644 index b34d523db3..0000000000 --- a/src/utils/monaco/yql/yqlSuggestions.ts +++ /dev/null @@ -1,174 +0,0 @@ -import type {CursorPosition} from '@gravity-ui/websql-autocomplete/shared'; -import type Monaco from 'monaco-editor'; - -import { - generateAggregateFunctionsSuggestion, - generateColumnAliasesSuggestion, - generateColumnsSuggestion, - generateEntitiesSuggestion, - generateEntitySettingsSuggestion, - generateKeywordsSuggestion, - generatePragmasSuggestion, - generateSimpleFunctionsSuggestion, - generateSimpleTypesSuggestion, - generateTableFunctionsSuggestion, - generateUdfSuggestion, - generateVariableSuggestion, - generateWindowFunctionsSuggestion, -} from './generateSuggestions'; - -export function createProvideSuggestionsFunction(database: string) { - return async ( - model: Monaco.editor.ITextModel, - monacoCursorPosition: Monaco.Position, - _context: Monaco.languages.CompletionContext, - _token: Monaco.CancellationToken, - ) => { - const rangeToInsertSuggestion = getRangeToInsertSuggestion(model, monacoCursorPosition); - - const suggestions = await getSuggestions( - model, - monacoCursorPosition, - rangeToInsertSuggestion, - database, - ); - - return {suggestions}; - }; -} - -function getEntityNameAtCursor(model: Monaco.editor.ITextModel, cursorPosition: Monaco.Position) { - const prevWord = model.findPreviousMatch( - '\\s(`?[^\\s]*)', - cursorPosition, - true, - false, - null, - true, - ); - const nextWord = model.findNextMatch('([^\\s]*)`?', cursorPosition, true, false, null, true); - - return `${prevWord?.matches?.[1] ?? ''}${nextWord?.matches?.[1] ?? ''}`; -} - -async function getSuggestions( - model: Monaco.editor.ITextModel, - cursorPosition: Monaco.Position, - rangeToInsertSuggestion: Monaco.IRange, - database: string, -): Promise { - const {parseYqlQuery} = await import('@gravity-ui/websql-autocomplete/yql'); - const cursorForParsing: CursorPosition = { - line: cursorPosition.lineNumber, - column: cursorPosition.column, - }; - const parseResult = parseYqlQuery(model.getValue(), cursorForParsing); - let entitiesSuggestions: Monaco.languages.CompletionItem[] = []; - let functionsSuggestions: Monaco.languages.CompletionItem[] = []; - let aggregateFunctionsSuggestions: Monaco.languages.CompletionItem[] = []; - let windowFunctionsSuggestions: Monaco.languages.CompletionItem[] = []; - let tableFunctionsSuggestions: Monaco.languages.CompletionItem[] = []; - let udfsSuggestions: Monaco.languages.CompletionItem[] = []; - let simpleTypesSuggestions: Monaco.languages.CompletionItem[] = []; - let pragmasSuggestions: Monaco.languages.CompletionItem[] = []; - let entitySettingsSuggestions: Monaco.languages.CompletionItem[] = []; - let variableSuggestions: Monaco.languages.CompletionItem[] = []; - - if (parseResult.suggestEntity) { - const entityNamePrefix = getEntityNameAtCursor(model, cursorPosition); - - entitiesSuggestions = await generateEntitiesSuggestion( - rangeToInsertSuggestion, - parseResult.suggestEntity, - database, - entityNamePrefix, - ); - } - if (parseResult.suggestFunctions) { - functionsSuggestions = await generateSimpleFunctionsSuggestion(rangeToInsertSuggestion); - } - if (parseResult.suggestAggregateFunctions) { - aggregateFunctionsSuggestions = - await generateAggregateFunctionsSuggestion(rangeToInsertSuggestion); - } - if (parseResult.suggestWindowFunctions) { - windowFunctionsSuggestions = - await generateWindowFunctionsSuggestion(rangeToInsertSuggestion); - } - if (parseResult.suggestTableFunctions) { - tableFunctionsSuggestions = await generateTableFunctionsSuggestion(rangeToInsertSuggestion); - } - if (parseResult.suggestSimpleTypes) { - simpleTypesSuggestions = await generateSimpleTypesSuggestion(rangeToInsertSuggestion); - } - if (parseResult.suggestVariables) { - variableSuggestions = generateVariableSuggestion( - rangeToInsertSuggestion, - parseResult.suggestVariables, - ); - } - if (parseResult.suggestUdfs) { - udfsSuggestions = await generateUdfSuggestion(rangeToInsertSuggestion); - } - if (parseResult.suggestPragmas) { - pragmasSuggestions = await generatePragmasSuggestion(rangeToInsertSuggestion); - } - if (parseResult.suggestEntitySettings) { - entitySettingsSuggestions = await generateEntitySettingsSuggestion( - rangeToInsertSuggestion, - parseResult.suggestEntitySettings, - ); - } - - const columnAliasSuggestion = await generateColumnAliasesSuggestion( - rangeToInsertSuggestion, - parseResult.suggestColumnAliases, - ); - - const columnsSuggestions = await generateColumnsSuggestion( - rangeToInsertSuggestion, - parseResult.suggestColumns, - parseResult.suggestVariables, - database, - ); - - const keywordsSuggestions = generateKeywordsSuggestion( - rangeToInsertSuggestion, - parseResult.suggestKeywords, - ); - - const suggestions: Monaco.languages.CompletionItem[] = [ - ...entitiesSuggestions, - ...functionsSuggestions, - ...windowFunctionsSuggestions, - ...tableFunctionsSuggestions, - ...udfsSuggestions, - ...simpleTypesSuggestions, - ...pragmasSuggestions, - ...columnAliasSuggestion, - ...columnsSuggestions, - ...keywordsSuggestions, - ...aggregateFunctionsSuggestions, - ...entitySettingsSuggestions, - ...variableSuggestions, - ]; - - return suggestions; -} - -function getRangeToInsertSuggestion( - model: Monaco.editor.ITextModel, - cursorPosition: Monaco.Position, -): Monaco.IRange { - const {startColumn: lastWordStartColumn, endColumn: lastWordEndColumn} = - model.getWordUntilPosition(cursorPosition); - // https://github.com/microsoft/Monaco-editor/discussions/3639#discussioncomment-5190373 if user already typed "$" sign, it should not be duplicated - const dollarBeforeLastWordStart = - model.getLineContent(cursorPosition.lineNumber)[lastWordStartColumn - 2] === '$' ? 1 : 0; - return { - startColumn: lastWordStartColumn - dollarBeforeLastWordStart, - startLineNumber: cursorPosition.lineNumber, - endColumn: lastWordEndColumn, - endLineNumber: cursorPosition.lineNumber, - }; -}