@kusto/monaco-kusto
Version:
CSL, KQL plugin for the Monaco Editor
973 lines (945 loc) • 38.7 kB
JavaScript
/*!-----------------------------------------------------------------------------
* 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 ? `` : '';
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 };