UNPKG

@kusto/monaco-kusto

Version:

CSL, KQL plugin for the Monaco Editor

973 lines (945 loc) 38.7 kB
/*!----------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * monaco-kusto version: 13.1.1(178105a761985a9b7c16d45b528f829e1c112ff0) * Released under the MIT license * https://https://github.com/Azure/monaco-kusto/blob/master/README.md *-----------------------------------------------------------------------------*/ import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import { languages } from 'monaco-editor/esm/vs/editor/editor.api'; import * as ls from 'vscode-languageserver-types'; import debounce from 'lodash-es/debounce'; import { L as LANGUAGE_ID, T as Token, t as tokenTypes } from './globals-ab5bacd5.js'; class WorkerManager { constructor(_monacoInstance, defaults) { this._monacoInstance = _monacoInstance; this._defaults = defaults; this._idleCheckInterval = self.setInterval(() => this._checkIfIdle(), 30 * 1000); this._configChangeListener = this._defaults.onDidChange(() => this._saveStateAndStopWorker()); } _stopWorker() { const workerToStop = this._workerDetails; this._workerDetailsPromise = null; this._workerDetails = null; // Ensure disposal occurs only after the last request completes. // This is necessary because setting the languageSettings disposes of the worker, // causing the setSchema call to remain unresolved, which prevents the semantic tokens provider from being registered. setTimeout(async () => { if (workerToStop._worker) { workerToStop._worker.dispose(); } }, 5000); } _saveStateAndStopWorker() { if (!this._workerDetails?._worker) { return; } this._workerDetails?._worker.getProxy().then(proxy => { proxy.getSchema().then(schema => { this._storedState = { schema: schema }; this._stopWorker(); }); }); } dispose() { clearInterval(this._idleCheckInterval); this._configChangeListener.dispose(); this._stopWorker(); } _checkIfIdle() { if (!this._workerDetails?._worker) { return; } const maxIdleTime = this._defaults.getWorkerMaxIdleTime(); let timePassedSinceLastUsed = Date.now() - this._workerDetails?._lastUsedTime; if (maxIdleTime > 0 && timePassedSinceLastUsed > maxIdleTime) { this._saveStateAndStopWorker(); } } _getClient() { // Since onDidProvideCompletionItems is not used in web worker, and since functions cannot be trivially serialized (throws exception unable to clone), We remove it here. const { onDidProvideCompletionItems, ...languageSettings } = this._defaults.languageSettings; if (!this._workerDetailsPromise) { const worker = this._monacoInstance.editor.createWebWorker({ // module that exports the create() method and returns a `KustoWorker` instance moduleId: 'vs/language/kusto/kustoWorker', label: 'kusto', // passed in to the create() method createData: { languageSettings: languageSettings, languageId: 'kusto' } }); const client = worker.getProxy().then(proxy => { // push state we held onto before killing the client. if (this._storedState) { return proxy.setSchema(this._storedState.schema).then(() => proxy); } else { return proxy; } }); this._workerDetailsPromise = client.then(client => { this._workerDetails = { _worker: worker, _client: client, _lastUsedTime: Date.now() }; return this._workerDetails; }); } return this._workerDetailsPromise; } getLanguageServiceWorker(...resources) { let _client; return this._getClient().then(client => { _client = client._client; }).then(_ => { return this._workerDetails?._worker?.withSyncedResources(resources); }).then(_ => _client); } } const createCompletionCacheManager = getFromLanguageService => { let completionList; let lastWord; let lastPosition; return { getCompletionItems: (word, resource, position) => { const didLinePositionChanged = !lastPosition || lastPosition.line !== position.line; const shouldGetItems = didLinePositionChanged || !lastWord || !word || !word?.includes(lastWord); if (shouldGetItems) { completionList = getFromLanguageService(resource, position); } lastWord = word; lastPosition = position; return completionList; } }; }; function createCompletionFilteredText(userInput, completionItem) { if (!userInput) return completionItem.filterText; const containedInFilterText = completionItem.filterText.toLowerCase().includes(userInput.toLowerCase()); if (!containedInFilterText) return completionItem.filterText; return `${userInput}${completionItem.filterText}`; } function getFocusedItem(completionItems, userInput) { const firstCompletionItem = completionItems[0]; if (!userInput) return firstCompletionItem; const firstMatchingItem = completionItems.find(item => item.filterText?.toLowerCase().startsWith(userInput.toLowerCase())); return firstMatchingItem ?? firstCompletionItem; } // --- diagnostics --- class DiagnosticsAdapter { _disposables = []; _contentListener = Object.create(null); _configurationListener = Object.create(null); _schemaListener = Object.create(null); _cursorListener = Object.create(null); _debouncedValidations = Object.create(null); constructor(_monacoInstance, _languageId, _worker, defaults, onSchemaChange) { this._monacoInstance = _monacoInstance; this._languageId = _languageId; this._worker = _worker; this.defaults = defaults; const onModelAdd = model => { let languageId = model.getLanguageId(); const modelUri = model.uri.toString(); if (languageId !== this._languageId) { return; } const debouncedValidation = this.getOrCreateDebouncedValidation(model, languageId); this._contentListener[modelUri] = model.onDidChangeContent(e => { const intervalsToValidate = changeEventToIntervals(e); debouncedValidation(intervalsToValidate); }); this._configurationListener[modelUri] = this.defaults.onDidChange(() => { self.setTimeout(() => this._doValidate(model, languageId, []), 0); }); this._schemaListener[modelUri] = onSchemaChange(() => { self.setTimeout(() => this._doValidate(model, languageId, []), 0); }); }; const onEditorAdd = editor => { const editorId = editor.getId(); if (!this._cursorListener[editorId]) { editor.onDidDispose(() => { this._cursorListener[editorId]?.dispose(); delete this._cursorListener[editorId]; }); this._cursorListener[editorId] = editor.onDidChangeCursorSelection(e => { const model = editor.getModel(); const languageId = model.getLanguageId(); if (languageId !== this._languageId) { return; } const cursorOffset = model.getOffsetAt(e.selection.getPosition()); const debouncedValidation = this.getOrCreateDebouncedValidation(model, languageId); debouncedValidation([{ start: cursorOffset, end: cursorOffset }]); }); } }; const onModelRemoved = model => { this._monacoInstance.editor.setModelMarkers(model, this._languageId, []); let uriStr = model.uri.toString(); let contentListener = this._contentListener[uriStr]; if (contentListener) { contentListener.dispose(); delete this._contentListener[uriStr]; } let configurationListener = this._configurationListener[uriStr]; if (configurationListener) { configurationListener.dispose(); delete this._configurationListener[uriStr]; } let schemaListener = this._schemaListener[uriStr]; if (schemaListener) { schemaListener.dispose(); delete this._schemaListener[uriStr]; } let debouncedValidation = this._debouncedValidations[uriStr]; if (debouncedValidation) { debouncedValidation.cancel(); delete this._debouncedValidations[uriStr]; } }; if (this.defaults.languageSettings.enableQuickFixes) { this._disposables.push(monaco.languages.registerCodeActionProvider(this._languageId, { provideCodeActions: async (model, range, context, _token) => { const startOffset = model.getOffsetAt(range.getStartPosition()); const endOffset = model.getOffsetAt(range.getEndPosition()); // We want to show the quick fix option only if we have markers in our selected range const showQuickFix = context.markers.length > 0; const actions = await this.getMonacoCodeActions(model, startOffset, endOffset, showQuickFix); return { actions, dispose: () => {} }; } })); } this._disposables.push(this._monacoInstance.editor.onDidCreateEditor(onEditorAdd)); this._disposables.push(this._monacoInstance.editor.onDidCreateModel(onModelAdd)); this._disposables.push(this._monacoInstance.editor.onWillDisposeModel(onModelRemoved)); this._disposables.push(this._monacoInstance.editor.onDidChangeModelLanguage(event => { onModelRemoved(event.model); onModelAdd(event.model); })); this._disposables.push({ dispose: () => { for (let key in this._contentListener) { this._contentListener[key].dispose(); } for (let key in this._cursorListener) { this._cursorListener[key].dispose(); } for (let key in this._debouncedValidations) { this._debouncedValidations[key].cancel(); } } }); this._monacoInstance.editor.getModels().forEach(onModelAdd); this._monacoInstance.editor.getEditors().forEach(onEditorAdd); } async getMonacoCodeActions(model, startOffset, endOffset, enableQuickFix) { const actions = []; const worker = await this._worker(model.uri); const resource = model.uri; const codeActions = await worker.getResultActions(resource.toString(), startOffset, endOffset); for (let i = 0; i < codeActions.length; i++) { const codeAction = codeActions[i]; if (codeAction.kind.includes('Extract Function')) { // Ignore extract function actions for now since they are buggy currently continue; } const codeActionKind = this.defaults.languageSettings.quickFixCodeActions?.find(actionKind => codeAction.kind.includes(actionKind)) ? 'quickfix' : 'custom'; if (codeActionKind === 'quickfix' && !enableQuickFix) { return; } const changes = codeAction.changes; const edits = changes.map(change => { const startPosition = model.getPositionAt(change.start); const endPosition = model.getPositionAt(change.start + change.deleteLength); return { resource: model.uri, textEdit: { range: { startLineNumber: startPosition.lineNumber, startColumn: startPosition.column, endLineNumber: endPosition.lineNumber, endColumn: endPosition.column }, text: change.insertText ?? '' } }; }); actions.push({ title: codeAction.title, diagnostics: [], kind: codeActionKind, edit: { edits: [...edits] } }); } return actions; } getOrCreateDebouncedValidation(model, languageId) { const modelUri = model.uri.toString(); if (!this._debouncedValidations[modelUri]) { this._debouncedValidations[modelUri] = debounce(intervals => this._doValidate(model, languageId, intervals), 500); } return this._debouncedValidations[modelUri]; } dispose() { this._disposables.forEach(d => d && d.dispose()); this._disposables = []; } _doValidate(model, languageId, intervals) { if (model.isDisposed()) { return; } const resource = model.uri; const versionNumberBefore = model.getVersionId(); this._worker(resource).then(worker => { return worker.doValidation(resource.toString(), intervals); }).then(diagnostics => { const newModel = this._monacoInstance.editor.getModel(resource); const versionId = newModel.getVersionId(); if (versionId !== versionNumberBefore) { return; } const markers = diagnostics.map(d => toDiagnostics(resource, d)); let model = this._monacoInstance.editor.getModel(resource); let oldDecorations = model.getAllDecorations().filter(decoration => decoration.options.className == 'squiggly-error').map(decoration => decoration.id); if (model && model.getLanguageId() === languageId) { const syntaxErrorAsMarkDown = this.defaults.languageSettings.syntaxErrorAsMarkDown; if (!syntaxErrorAsMarkDown || !syntaxErrorAsMarkDown.enableSyntaxErrorAsMarkDown) { // Remove previous syntax error decorations and set the new markers (for example, when disabling syntaxErrorAsMarkDown after it was enabled) model.deltaDecorations(oldDecorations, []); this._monacoInstance.editor.setModelMarkers(model, languageId, markers); } else { // Add custom popup for syntax error: icon, header and message as markdown const header = syntaxErrorAsMarkDown.header ? `**${syntaxErrorAsMarkDown.header}** \n\n` : ''; const icon = syntaxErrorAsMarkDown.icon ? `![](${syntaxErrorAsMarkDown.icon})` : ''; const popupErrorHoverHeaderMessage = `${icon} ${header}`; const newDecorations = markers.map(marker => { return { range: { startLineNumber: marker.startLineNumber, startColumn: marker.startColumn, endLineNumber: marker.endLineNumber, endColumn: marker.endColumn }, options: { hoverMessage: { value: popupErrorHoverHeaderMessage + marker.message }, className: 'squiggly-error', // monaco syntax error style (red underline) zIndex: 100, // This message will be the upper most mesage in the popup overviewRuler: { // The color indication on the right ruler color: 'rgb(255, 18, 18, 0.7)', position: monaco.editor.OverviewRulerLane.Right }, minimap: { color: 'rgb(255, 18, 18, 0.7)', position: monaco.editor.MinimapPosition.Inline } } }; }); const oldMarkers = monaco.editor.getModelMarkers({ owner: languageId, resource: resource }); if (oldMarkers && oldMarkers.length > 0) { // In case there were previous markers, remove their decorations (for example, when enabling syntaxErrorAsMarkDown after it was disabled) oldDecorations = []; // Remove previous markers this._monacoInstance.editor.setModelMarkers(model, languageId, []); } // Remove previous syntax error decorations and set the new decorations model.deltaDecorations(oldDecorations, newDecorations); } } }).then(undefined, err => { console.error(err); }); } } function changeEventToIntervals(e) { return e.changes.map(change => ({ start: change.rangeOffset, end: change.rangeOffset + change.text.length })); } function toSeverity(lsSeverity) { switch (lsSeverity) { case ls.DiagnosticSeverity.Error: return monaco.MarkerSeverity.Error; case ls.DiagnosticSeverity.Warning: return monaco.MarkerSeverity.Warning; case ls.DiagnosticSeverity.Information: return monaco.MarkerSeverity.Info; case ls.DiagnosticSeverity.Hint: return monaco.MarkerSeverity.Hint; default: return monaco.MarkerSeverity.Info; } } function toDiagnostics(resource, diag) { let code = typeof diag.code === 'number' ? String(diag.code) : diag.code; return { severity: toSeverity(diag.severity), startLineNumber: diag.range.start.line + 1, startColumn: diag.range.start.character + 1, endLineNumber: diag.range.end.line + 1, endColumn: diag.range.end.character + 1, message: diag.message, code: code, source: diag.source }; } // --- completion ------ function fromPosition(position) { if (!position) { return void 0; } return { character: position.column - 1, line: position.lineNumber - 1 }; } function fromRange(range) { if (!range) { return void 0; } return { start: fromPosition(range.getStartPosition()), end: fromPosition(range.getEndPosition()) }; } function toRange(range) { if (!range) { return void 0; } return new monaco.Range(range.start.line + 1, range.start.character + 1, range.end.line + 1, range.end.character + 1); } function toCompletionItemKind(kind) { let mItemKind = monaco.languages.CompletionItemKind; switch (kind) { case ls.CompletionItemKind.Text: return mItemKind.Text; case ls.CompletionItemKind.Method: return mItemKind.Method; case ls.CompletionItemKind.Function: return mItemKind.Function; case ls.CompletionItemKind.Constructor: return mItemKind.Constructor; case ls.CompletionItemKind.Field: return mItemKind.Field; case ls.CompletionItemKind.Variable: return mItemKind.Variable; case ls.CompletionItemKind.Class: return mItemKind.Class; case ls.CompletionItemKind.Interface: return mItemKind.Interface; case ls.CompletionItemKind.Module: return mItemKind.Module; case ls.CompletionItemKind.Property: return mItemKind.Property; case ls.CompletionItemKind.Unit: return mItemKind.Unit; case ls.CompletionItemKind.Value: return mItemKind.Value; case ls.CompletionItemKind.Enum: return mItemKind.Enum; case ls.CompletionItemKind.Keyword: return mItemKind.Keyword; case ls.CompletionItemKind.Snippet: return mItemKind.Snippet; case ls.CompletionItemKind.Color: return mItemKind.Color; case ls.CompletionItemKind.File: return mItemKind.File; case ls.CompletionItemKind.Reference: return mItemKind.Reference; } return mItemKind.Property; } function toTextEdit(textEdit) { if (!textEdit) { return void 0; } return { range: toRange(textEdit.range), text: textEdit.newText }; } const DEFAULT_DOCS_BASE_URL = 'https://learn.microsoft.com/azure/data-explorer/kusto/query'; class CompletionAdapter { constructor(workerAccessor, languageSettings) { this.languageSettings = languageSettings; const getFromLanguageService = async (resource, position) => { const worker = await workerAccessor(resource); return worker.doComplete(resource.toString(), position); }; this.completionCacheManager = createCompletionCacheManager(getFromLanguageService); } get triggerCharacters() { return [' ', '.', '(']; } provideCompletionItems(model, position, context, token) { const wordInfo = model.getWordUntilPosition(position); const wordRange = new monaco.Range(position.lineNumber, wordInfo.startColumn, position.lineNumber, wordInfo.endColumn); const resource = model.uri; const userInput = model?.getWordAtPosition(position)?.word; const onDidProvideCompletionItems = this.languageSettings.onDidProvideCompletionItems; return this.completionCacheManager.getCompletionItems(userInput, resource, fromPosition(position)).then(info => onDidProvideCompletionItems ? onDidProvideCompletionItems(info) : info).then(info => { if (!info) return; const selectedItem = getFocusedItem(info.items, userInput); let items = info.items.map((entry, index) => { let item = { label: entry.label, insertText: entry.insertText, sortText: entry.sortText, filterText: createCompletionFilteredText(userInput, entry), // TODO: Is this cast safe? documentation: this.formatDocLink(entry.documentation?.value), detail: entry.detail, range: wordRange, kind: toCompletionItemKind(entry.kind), preselect: selectedItem.filterText === entry.filterText }; if (entry.textEdit) { // TODO: Where is the "range" property coming from? item.range = toRange(entry.textEdit.range); item.insertText = entry.textEdit.newText; } if (entry.insertTextFormat === ls.InsertTextFormat.Snippet) { item.insertTextRules = monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet; } return item; }); return { incomplete: true, suggestions: items }; }); } formatDocLink(docString) { // If the docString is empty, we want to return undefined to prevent an empty documentation popup. if (!docString) { return undefined; } const { documentationBaseUrl = DEFAULT_DOCS_BASE_URL, documentationSuffix } = this.languageSettings; const urisProxy = new Proxy({}, { get(_target, prop, _receiver) { // The link comes with a postfix of ".md" that we want to remove let url = prop.toString().replace('.md', ''); // Sometimes we get the link as a full URL. For example in the main doc link of the item if (!url.startsWith('https')) { url = `${documentationBaseUrl}/${url}`; } const monacoUri = monaco.Uri.parse(url); if (documentationSuffix) { // We need to override the toString method to add the suffix, otherwise it gets encoded and page doesn't open monacoUri.toString = () => url + documentationSuffix; } return monacoUri; } }); return { value: docString, isTrusted: true, uris: urisProxy }; } } function isMarkupContent(thing) { return thing && typeof thing === 'object' && typeof thing.kind === 'string'; } function toMarkdownString(entry) { if (typeof entry === 'string') { return { value: entry }; } if (isMarkupContent(entry)) { if (entry.kind === 'plaintext') { return { value: entry.value.replace(/[\\`*_{}[\]()#+\-.!]/g, '\\$&') }; } return { value: entry.value }; } return { value: '```' + entry.value + '\n' + entry.value + '\n```\n' }; } function toMarkedStringArray(contents) { if (!contents) { return void 0; } if (Array.isArray(contents)) { return contents.map(toMarkdownString); } return [toMarkdownString(contents)]; } // --- definition ------ function toLocation(location) { return { uri: monaco.Uri.parse(location.uri), range: toRange(location.range) }; } class DefinitionAdapter { constructor(_worker) { this._worker = _worker; } provideDefinition(model, position, token) { const resource = model.uri; return this._worker(resource).then(worker => { return worker.findDefinition(resource.toString(), fromPosition(position)); }).then(definition => { if (!definition || definition.length == 0) { return; } return [toLocation(definition[0])]; }); } } // --- references ------ class ReferenceAdapter { constructor(_worker) { this._worker = _worker; } provideReferences(model, position, context, token) { const resource = model.uri; return this._worker(resource).then(worker => { return worker.findReferences(resource.toString(), fromPosition(position)); }).then(entries => { if (!entries) { return; } return entries.map(toLocation); }); } } // --- rename ------ function toWorkspaceEdit(edit) { if (!edit || !edit.changes) { return void 0; } let resourceEdits = []; for (let uri in edit.changes) { const _uri = monaco.Uri.parse(uri); for (let e of edit.changes[uri]) { resourceEdits.push({ resource: _uri, textEdit: { range: toRange(e.range), text: e.newText }, versionId: undefined }); } } return { edits: resourceEdits }; } class RenameAdapter { constructor(_worker) { this._worker = _worker; } provideRenameEdits(model, position, newName, token) { const resource = model.uri; return this._worker(resource).then(worker => { return worker.doRename(resource.toString(), fromPosition(position), newName); }).then(edit => { return toWorkspaceEdit(edit); }); } } // --- formatting ----- class DocumentFormatAdapter { constructor(_worker) { this._worker = _worker; } provideDocumentFormattingEdits(model, options, token) { const resource = model.uri; return this._worker(resource).then(worker => { return worker.doDocumentFormat(resource.toString()).then(edits => edits.map(edit => toTextEdit(edit))); }); } } class FormatAdapter { constructor(_worker) { this._worker = _worker; } provideDocumentRangeFormattingEdits(model, range, options, token) { const resource = model.uri; return this._worker(resource).then(worker => { return worker.doRangeFormat(resource.toString(), fromRange(range)).then(edits => edits.map(edit => toTextEdit(edit))); }); } } // --- Folding --- class FoldingAdapter { constructor(_worker) { this._worker = _worker; } provideFoldingRanges(model, context, token) { const resource = model.uri; return this._worker(resource).then(worker => { return worker.doFolding(resource.toString()).then(foldingRanges => foldingRanges.map(range => toFoldingRange(range))); }); } } function toFoldingRange(range) { return { start: range.startLine + 1, end: range.endLine + 1, kind: monaco.languages.FoldingRangeKind.Region }; } // --- hover ------ class HoverAdapter { constructor(_worker) { this._worker = _worker; } provideHover(model, position, token) { let resource = model.uri; return this._worker(resource).then(worker => { return worker.doHover(resource.toString(), fromPosition(position)); }).then(info => { if (!info) { return; } return { range: toRange(info.range), contents: toMarkedStringArray(info.contents) }; }); } } const queryOperators = ['as', 'consume', 'distinct', 'evaluate', 'extend', 'getschema', 'graph-match', 'graph-merge', 'graph-to-table', 'invoke', 'join', 'limit', 'lookup', 'make-graph', 'make-series', 'mv-apply', 'mv-expand', 'order', 'parse', 'parse-kv', 'parse-where', 'project', 'project-away', 'project-keep', 'project-rename', 'project-reorder', 'range', 'reduce', 'render', 'sample', 'sample-distinct', 'scan', 'serialize', 'sort', 'summarize', 'take', 'top', 'top-hitters', 'top-nested', 'union', 'where', 'filter', 'fork', 'facet', 'range', 'consume', 'find', 'search', 'print', 'partition', 'lookup']; const queryParameters = ['kind']; const types = ['bool', 'datetime', 'decimal', 'double', 'dynamic', 'guid', 'int', 'long', 'real', 'string', 'timespan']; const commands = ['.add', '.alter', '.alter-merge', '.append', '.as', '.assert', '.attach', '.consume', '.count', '.create', '.create-merge', '.create-or-alter', '.create-set', '.datatable', '.default', '.define', '.delete', '.detach', '.distinct', '.drop', '.drop-pretend', '.dup-next-failed-ingest', '.dup-next-ingest', '.evaluate', '.export', '.extend', '.externaldata', '.filter', '.find', '.fork', '.getschema', '.ingest', '.join', '.limit', '.load', '.make-series', '.materialize', '.move', '.mv-expand', '.order', '.parse', '.parse-where', '.partition', '.pivot', '.print', '.project', '.project-away', '.project-keep', '.project-rename', '.reduce', '.remove', '.rename', '.replace', '.restrict', '.run', '.sample', '.sample-distinct', '.save', '.search', '.serialize', '.set', '.set-or-append', '.set-or-replace', '.show', '.sort', '.summarize', '.take', '.top', '.top-hitters', '.top-nested', '.union']; const functions = ['abs', 'acos', 'ago', 'array_concat', 'array_length', 'array_slice', 'array_split', 'asin', 'atan', 'atan2', 'avg', 'bag_keys', 'base64_decodestring', 'base64_encodestring', 'bin', 'bin_at', 'binary_and', 'binary_not', 'binary_or', 'binary_shift_left', 'binary_shift_right', 'binary_xor', 'case', 'ceiling', 'coalesce', 'columnifexists', 'cos', 'count', 'countof', 'cot', 'cursor_after', 'datatable', 'datepart', 'datetime_add', 'datetime_diff', 'datetime_part', 'dayofmonth', 'dayofweek', 'dayofyear', 'dcount', 'dcount_hll', 'degrees', 'endofday', 'endofmonth', 'endofweek', 'endofyear', 'exp', 'exp10', 'exp2', 'extract', 'extractall', 'extractjson', 'format_datetime', 'format_timespan', 'floor', 'gamma', 'geo_distance_2points', 'geo_geohash_to_central_point', 'geo_point_in_circle', 'geo_point_in_polygon', 'geo_point_to_geohash', 'getmonth', 'gettype', 'getyear', 'hash', 'hash_sha256', 'hll_merge', 'iif', 'indexof', 'isempty', 'isfinite', 'isinf', 'isascii', 'isnan', 'isnotempty', 'isnotnull', 'isnull', 'isutf8', 'log', 'log10', 'log2', 'loggamma', 'make_datetime', 'make_string', 'make_timespan', 'materialize', 'max', 'max_of', 'min', 'min_of', 'monthofyear', 'next', 'not', 'pack', 'pack_array', 'pack_dictionary', 'parse_csv', 'parse_ipv4', 'parse_json', 'parse_path', 'parse_url', 'parse_urlquery', 'parse_user_agent', 'parse_version', 'parse_xml', 'parsejson', 'percentrank_tdigest', 'percentile_tdigest', 'pow', 'prev', 'radians', 'rand', 'rank_tdigest', 'repeat', 'replace', 'reverse', 'round', 'row_cumsum', 'row_window_session', 'series_add', 'series_decompose', 'series_decompose_anomalies', 'series_decompose_forecast', 'series_divide', 'series_equals', 'series_fill_backward', 'series_fill_const', 'series_fill_forward', 'series_fill_linear', 'series_fir', 'series_fit_2lines', 'series_fit_2lines_dynamic', 'series_fit_line', 'series_fit_line_dynamic', 'series_greater', 'series_greater_equals', 'series_iir', 'series_less', 'series_less_equals', 'series_multiply', 'series_not_equals', 'series_outliers', 'series_pearson_correlation', 'series_periods_detect', 'series_periods_validate', 'series_seasonal', 'series_stats', 'series_stats_dynamic', 'series_subtract', 'sign', 'sin', 'split', 'sqrt', 'startofday', 'startofmonth', 'startofweek', 'startofyear', 'strcat', 'strcat_array', 'strcat_delim', 'strcmp', 'strlen', 'strrep', 'string_size', 'substring', 'sum', 'tan', 'tdigest_merge', 'tobool', 'toboolean', 'todecimal', 'todouble', 'todynamic', 'tofloat', 'toguid', 'tohex', 'toint', 'tolong', 'tolower', 'toobject', 'toreal', 'toscalar', 'tostring', 'totimespan', 'toupper', 'translate', 'trim', 'trim_end', 'trim_start', 'typeof', 'url_decode', 'url_encode', 'week_of_year', 'welch_test']; const keywords = ['and', 'as', 'asc', 'between', 'by', 'contains', 'count', 'desc', 'extend', 'false', 'filter', 'find', 'from', 'has', 'in', 'inner', 'join', 'leftouter', 'let', 'not', 'on', 'or', 'policy', 'project', 'project-away', 'project-rename', 'project-reorder', 'project-keep', 'range', 'rename', 'retention', 'summarize', 'table', 'take', 'to', 'true', 'where', 'with']; const kustoLanguageDefinition = { name: LANGUAGE_ID, mimeTypes: ['text/kusto'], displayName: 'Kusto', defaultToken: 'invalid', queryOperators, queryParameters, types, commands, functions, keywords, tokenizer: { root: [[/(\/\/.*$)/, Token.Comment], [/[\(\)\{\}\|\[\]\:\=\,\<|\.\..]/, Token.Punctuation], [/[\+\-\*\/\%\!\<\<=\>\>=\=\==\!=\<>\:\;\,\=~\@\?\=>\!~]/, Token.MathOperator], [/"([^"\\]*(\\.[^"\\]*)*)"/, Token.StringLiteral], [/'([^"\\]*(\\.[^"\\]*)*)'/, Token.StringLiteral], [/[\w@#\-$\.]+/, { cases: { '@queryOperators': Token.QueryOperator, '@queryParameters': Token.QueryParameter, '@types': Token.Type, '@commands': Token.Command, '@functions': Token.Function, '@keywords': Token.Keyword, '@default': 'identifier' } }]] } }; class SemanticTokensProvider { constructor(classificationsGetter) { this.classificationsGetter = classificationsGetter; } getLegend() { return { tokenTypes, tokenModifiers: [] }; } async provideDocumentSemanticTokens(model) { const resource = model.uri; const classifications = await this.classificationsGetter(resource); const tokens = []; let prevLine = 0; let prevChar = 0; for (const classification of classifications) { const parts = toSemanticTokens(classification, model); for (const part of parts) { const [absLine, absChar, length, kind, modifiers] = part; const deltaLine = absLine - prevLine; const deltaChar = deltaLine === 0 ? absChar - prevChar : absChar; tokens.push([deltaLine, deltaChar, length, kind, modifiers]); prevLine = absLine; prevChar = absChar; } } return { data: new Uint32Array(tokens.flat(2)), resultId: model.getVersionId().toString() }; } releaseDocumentSemanticTokens() {} } function toSemanticTokens(classification, model) { const { line, character, length, kind } = classification; const tokens = []; let remainingLength = length; let currentLine = line; let currentChar = character; while (remainingLength > 0 && currentLine < model.getLineCount()) { const lineLength = model.getLineLength(currentLine + 1); const available = lineLength - currentChar + 1; const tokenLength = Math.min(remainingLength, available); tokens.push([currentLine, currentChar, tokenLength, kind, 0]); remainingLength -= tokenLength; currentLine++; currentChar = 0; // reset for next line } return tokens; } // Registers semantic token provider that utilizes the language service // for more context-relevant syntax highlighting. function semanticTokensProviderRegistrarCreator() { const semanticTokensProviderRegistrar = semanticTokensProviderRegistrarCreatorForTest(); return (monacoInstance, workerAccessor) => { const semanticTokensProvider = semanticTokensProviderMaker(workerAccessor); semanticTokensProviderRegistrar(monacoInstance, semanticTokensProvider); }; } function semanticTokensProviderRegistrarCreatorForTest() { let semanticTokensDisposable; return (monacoInstance, semanticTokensProvider) => { if (semanticTokensDisposable) { semanticTokensDisposable.dispose(); } semanticTokensDisposable = monacoInstance.languages.registerDocumentSemanticTokensProvider(LANGUAGE_ID, semanticTokensProvider); }; } function semanticTokensProviderMaker(workerAccessor) { const classificationsGetter = async resource => { const worker = await workerAccessor(resource); return worker.getClassifications(resource.toString()); }; return new SemanticTokensProvider(classificationsGetter); } languages.LanguageConfiguration; let kustoWorker; let resolveWorker; let workerPromise = new Promise((resolve, reject) => { resolveWorker = resolve; }); /** * Called when Kusto language is first needed (a model has the language set) * @param defaults */ function setupMode(defaults, monacoInstance) { const onSchemaChange = new monaco.Emitter(); const semanticTokensProviderRegistrar = semanticTokensProviderRegistrarCreator(); const client = new WorkerManager(monacoInstance, defaults); const workerAccessor = (first, ...more) => { const augmentedSetSchema = async (schema, worker) => { const workerPromise = worker.setSchema(schema); await workerPromise.then(() => { onSchemaChange.fire(schema); }); semanticTokensProviderRegistrar(monacoInstance, workerAccessor); }; const worker = client.getLanguageServiceWorker(...[first].concat(more)); return worker.then(worker => ({ ...worker, setSchema: schema => augmentedSetSchema(schema, worker), async setSchemaFromShowSchema(schema, connection, database, globalScalarParameters, globalTabularParameters) { await worker.normalizeSchema(schema, connection, database).then(schema => { if (globalScalarParameters || globalTabularParameters) { schema = { ...schema, globalScalarParameters, globalTabularParameters }; } augmentedSetSchema(schema, worker); }); } })); }; monacoInstance.languages.registerCompletionItemProvider(LANGUAGE_ID, new CompletionAdapter(workerAccessor, defaults.languageSettings)); monacoInstance.languages.setMonarchTokensProvider(LANGUAGE_ID, kustoLanguageDefinition); new DiagnosticsAdapter(monacoInstance, LANGUAGE_ID, workerAccessor, defaults, onSchemaChange.event); monacoInstance.languages.registerDocumentRangeFormattingEditProvider(LANGUAGE_ID, new FormatAdapter(workerAccessor)); monacoInstance.languages.registerFoldingRangeProvider(LANGUAGE_ID, new FoldingAdapter(workerAccessor)); monacoInstance.languages.registerDefinitionProvider(LANGUAGE_ID, new DefinitionAdapter(workerAccessor)); monacoInstance.languages.registerRenameProvider(LANGUAGE_ID, new RenameAdapter(workerAccessor)); monacoInstance.languages.registerReferenceProvider(LANGUAGE_ID, new ReferenceAdapter(workerAccessor)); if (defaults.languageSettings.enableHover) { monacoInstance.languages.registerHoverProvider(LANGUAGE_ID, new HoverAdapter(workerAccessor)); } monacoInstance.languages.registerDocumentFormattingEditProvider(LANGUAGE_ID, new DocumentFormatAdapter(workerAccessor)); kustoWorker = workerAccessor; resolveWorker(workerAccessor); monacoInstance.languages.setLanguageConfiguration(LANGUAGE_ID, languageConfiguration); } function getKustoWorker() { return workerPromise.then(() => kustoWorker); } const languageConfiguration = { folding: { offSide: false, markers: { start: /^\s*[\r\n]/gm, end: /^\s*[\r\n]/gm } }, comments: { lineComment: '//', blockComment: null }, autoClosingPairs: [{ open: '{', close: '}' }, { open: '[', close: ']' }, { open: '(', close: ')' }, { open: "'", close: "'", notIn: ['string', 'comment'] }, { open: '"', close: '"', notIn: ['string', 'comment'] }], brackets: [['[', ']'], ['{', '}'], ['(', ')']], colorizedBracketPairs: [], wordPattern: /[a-zA-Z0-9\-_]+/g }; export { getKustoWorker, setupMode };