diff --git a/README.md b/README.md index 8684c0bb..c2958dcf 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,11 @@ Every PR should come with a test that checks it. ## Changelog -### 12.0.0 +### 12.0.2 + +- fix: Revert the refactor as it caused build issues + +### 12.0.1 - fix: Resolved a race condition to ensure the semantic tokens provider is registered after setting the language server schema. - fix: Fixed an issue where semantic highlighting didn't work on schema change by properly handling provider registration. diff --git a/package/package.json b/package/package.json index 41477928..6eed982b 100644 --- a/package/package.json +++ b/package/package.json @@ -1,6 +1,6 @@ { "name": "@kusto/monaco-kusto", - "version": "12.0.1", + "version": "12.0.2", "description": "CSL, KQL plugin for the Monaco Editor", "author": { "name": "Microsoft" diff --git a/package/src/kustoMode.ts b/package/src/kustoMode.ts index 55fc9ec8..6c422baf 100644 --- a/package/src/kustoMode.ts +++ b/package/src/kustoMode.ts @@ -1,101 +1,149 @@ import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import { WorkerManager } from './workerManager'; -import { KustoWorker, LanguageServiceDefaults, showSchema } from './monaco.contribution'; +import type { KustoWorker, LanguageServiceDefaults } from './monaco.contribution'; import * as languageFeatures from './languageFeatures'; -import { Schema, ScalarParameter, TabularParameter } from './languageServiceManager/schema'; -import { IKustoWorkerImpl } from './kustoWorker'; +import type { Schema } from './languageServiceManager/schema'; +import type { IKustoWorkerImpl } from './kustoWorker'; +import { kustoLanguageDefinition } from './syntaxHighlighting/kustoMonarchLanguageDefinition'; import { LANGUAGE_ID } from './globals'; import { semanticTokensProviderRegistrarCreator } from './syntaxHighlighting/semanticTokensProviderRegistrar'; -import { kustoLanguageDefinition } from './syntaxHighlighting/kustoMonarchLanguageDefinition'; export interface AugmentedWorker extends KustoWorker, Omit {} export interface AugmentedWorkerAccessor { - (first: monaco.Uri): Promise; + (first: monaco.Uri, ...more: monaco.Uri[]): Promise; } -let workerAccessor: AugmentedWorkerAccessor; - -export async function setupMode( +let kustoWorker: AugmentedWorkerAccessor; +let resolveWorker: (value: AugmentedWorkerAccessor | PromiseLike) => void; +let rejectWorker: (err: any) => void; +let workerPromise: Promise = new Promise((resolve, reject) => { + resolveWorker = resolve; + rejectWorker = reject; +}); + +/** + * Called when Kusto language is first needed (a model has the language set) + * @param defaults + */ +export function setupMode( defaults: LanguageServiceDefaults, - monacoInstance: typeof monaco -): Promise { + monacoInstance: typeof globalThis.monaco +): AugmentedWorkerAccessor { let onSchemaChange = new monaco.Emitter(); - const client = new WorkerManager(monacoInstance, defaults); + // TODO: when should we dispose of these? seems like monaco-css and monaco-typescript don't dispose of these. + let disposables: monaco.IDisposable[] = []; const semanticTokensProviderRegistrar = semanticTokensProviderRegistrarCreator(); - workerAccessor = async (uri) => { - const worker = await client.getLanguageServiceWorker(uri); + const client = new WorkerManager(monacoInstance, defaults); + disposables.push(client); - const augmentedSetSchema = async (schema: Schema) => { - await worker.setSchema(schema); - onSchemaChange.fire(schema); - semanticTokensProviderRegistrar(monacoInstance, worker); - }; - const setSchemaFromShowSchema = async ( - schema: showSchema.Result, - clusterConnectionString: string, - databaseInContextName: string, - globalScalarParameters: ScalarParameter[], - globalTabularParameters: TabularParameter[] - ) => { - const normalizedSchema = await worker.normalizeSchema( - schema, - clusterConnectionString, - databaseInContextName - ); - normalizedSchema.globalScalarParameters = globalScalarParameters; - normalizedSchema.globalTabularParameters = globalTabularParameters; - await augmentedSetSchema(normalizedSchema); - }; + const workerAccessor: AugmentedWorkerAccessor = (first, ...more) => { + const augmentedSetSchema = async (schema: Schema, worker: KustoWorker) => { + const workerPromise = worker.setSchema(schema); - return { - ...worker, - setSchema: augmentedSetSchema, - setSchemaFromShowSchema, + await workerPromise.then(() => { + onSchemaChange.fire(schema); + }); + semanticTokensProviderRegistrar(monacoInstance, workerAccessor); }; + const worker = client.getLanguageServiceWorker(...[first].concat(more)); + return worker.then( + (worker): AugmentedWorker => ({ + ...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.setMonarchTokensProvider(LANGUAGE_ID, kustoLanguageDefinition); - - const completionAdapter = new languageFeatures.CompletionAdapter(workerAccessor, defaults.languageSettings); - monacoInstance.languages.registerCompletionItemProvider(LANGUAGE_ID, completionAdapter); + disposables.push( + monacoInstance.languages.registerCompletionItemProvider( + LANGUAGE_ID, + new languageFeatures.CompletionAdapter(workerAccessor, defaults.languageSettings) + ) + ); - // this constructor has side effects and therefore doesn't need to be passed anywhere - new languageFeatures.DiagnosticsAdapter( - monacoInstance, + const monarchTokensProvider = monacoInstance.languages.setMonarchTokensProvider( LANGUAGE_ID, - workerAccessor, - defaults, - onSchemaChange.event + kustoLanguageDefinition ); - const documentRangeFormattingAdapter = new languageFeatures.FormatAdapter(workerAccessor); - monacoInstance.languages.registerDocumentRangeFormattingEditProvider(LANGUAGE_ID, documentRangeFormattingAdapter); + disposables.push( + new languageFeatures.DiagnosticsAdapter( + monacoInstance, + LANGUAGE_ID, + workerAccessor, + defaults, + onSchemaChange.event + ) + ); - const foldingRangeAdapter = new languageFeatures.FoldingAdapter(workerAccessor); - monacoInstance.languages.registerFoldingRangeProvider(LANGUAGE_ID, foldingRangeAdapter); + disposables.push( + monacoInstance.languages.registerDocumentRangeFormattingEditProvider( + LANGUAGE_ID, + new languageFeatures.FormatAdapter(workerAccessor) + ) + ); - const definitionProvider = new languageFeatures.DefinitionAdapter(workerAccessor); - monacoInstance.languages.registerDefinitionProvider(LANGUAGE_ID, definitionProvider); + disposables.push( + monacoInstance.languages.registerFoldingRangeProvider( + LANGUAGE_ID, + new languageFeatures.FoldingAdapter(workerAccessor) + ) + ); - monacoInstance.languages.registerRenameProvider(LANGUAGE_ID, new languageFeatures.RenameAdapter(workerAccessor)); + disposables.push( + monacoInstance.languages.registerDefinitionProvider( + LANGUAGE_ID, + new languageFeatures.DefinitionAdapter(workerAccessor) + ) + ); - const referenceProvider = new languageFeatures.ReferenceAdapter(workerAccessor); - monacoInstance.languages.registerReferenceProvider(LANGUAGE_ID, referenceProvider); + disposables.push( + monacoInstance.languages.registerRenameProvider(LANGUAGE_ID, new languageFeatures.RenameAdapter(workerAccessor)) + ); + + disposables.push( + monacoInstance.languages.registerReferenceProvider( + LANGUAGE_ID, + new languageFeatures.ReferenceAdapter(workerAccessor) + ) + ); if (defaults.languageSettings.enableHover) { - const hoverAdapter = new languageFeatures.HoverAdapter(workerAccessor); - monacoInstance.languages.registerHoverProvider(LANGUAGE_ID, hoverAdapter); + disposables.push( + monacoInstance.languages.registerHoverProvider( + LANGUAGE_ID, + new languageFeatures.HoverAdapter(workerAccessor) + ) + ); } - const documentFormattingAdapter = new languageFeatures.DocumentFormatAdapter(workerAccessor); - monacoInstance.languages.registerDocumentFormattingEditProvider(LANGUAGE_ID, documentFormattingAdapter); + monacoInstance.languages.registerDocumentFormattingEditProvider( + LANGUAGE_ID, + new languageFeatures.DocumentFormatAdapter(workerAccessor) + ); + kustoWorker = workerAccessor; + resolveWorker(workerAccessor); - const languageConfiguration = { + monacoInstance.languages.setLanguageConfiguration(LANGUAGE_ID, { folding: { offSide: false, markers: { start: /^\s*[\r\n]/gm, end: /^\s*[\r\n]/gm }, @@ -111,12 +159,11 @@ export async function setupMode( { open: "'", close: "'", notIn: ['string', 'comment'] }, { open: '"', close: '"', notIn: ['string', 'comment'] }, ], - }; - monacoInstance.languages.setLanguageConfiguration(LANGUAGE_ID, languageConfiguration); + }); - return workerAccessor; + return kustoWorker; } -export async function getKustoWorker(): Promise { - return workerAccessor; +export function getKustoWorker(): Promise { + return workerPromise.then(() => kustoWorker); } diff --git a/package/src/languageFeatures.ts b/package/src/languageFeatures.ts index dc71afe1..de3b7c96 100644 --- a/package/src/languageFeatures.ts +++ b/package/src/languageFeatures.ts @@ -27,7 +27,7 @@ export class DiagnosticsAdapter { } = Object.create(null); constructor( - private _monacoInstance: typeof monaco, + private _monacoInstance: typeof globalThis.monaco, private _languageId: string, private _worker: AugmentedWorkerAccessor, private defaults: LanguageServiceDefaults, diff --git a/package/src/languageServiceManager/schema.ts b/package/src/languageServiceManager/schema.ts index b9d237ab..1dcc5ad2 100644 --- a/package/src/languageServiceManager/schema.ts +++ b/package/src/languageServiceManager/schema.ts @@ -73,8 +73,8 @@ export interface EngineSchema { readonly databases: readonly Database[]; }; readonly database: Database | undefined; // a reference to the database that's in current context. - globalScalarParameters?: readonly ScalarParameter[]; - globalTabularParameters?: readonly TabularParameter[]; + readonly globalScalarParameters?: readonly ScalarParameter[]; + readonly globalTabularParameters?: readonly TabularParameter[]; } export type TableEntityType = 'Table' | 'ExternalTable' | 'MaterializedViewTable'; diff --git a/package/src/monaco.contribution.ts b/package/src/monaco.contribution.ts index 2deea1db..dd3b340b 100644 --- a/package/src/monaco.contribution.ts +++ b/package/src/monaco.contribution.ts @@ -1,6 +1,6 @@ import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; -import * as mode from './kustoMode'; +import type * as mode from './kustoMode'; import KustoCommandHighlighter from './commandHighlighter'; import KustoCommandFormatter from './commandFormatter'; import { extend } from './extendedEditor'; @@ -17,6 +17,8 @@ export * from './languageServiceManager/settings'; export * from './types'; export * from './extendedGlobalApi'; +// --- Kusto configuration and defaults --------- + class LanguageServiceDefaultsImpl implements LanguageServiceDefaults { private _onDidChange = new monaco.Emitter(); private _languageSettings: LanguageSettings; @@ -78,14 +80,22 @@ const defaultLanguageSettings: LanguageSettings = { completionOptions: { includeExtendedSyntax: false }, }; -export async function getKustoWorker(): Promise { - return mode.getKustoWorker(); +export function getKustoWorker(): Promise { + return new Promise((resolve, reject) => { + withMode((mode) => { + mode.getKustoWorker().then(resolve, reject); + }); + }); +} + +function withMode(callback: (module: typeof mode) => void): void { + import('./kustoMode').then(callback); } export const kustoDefaults = new LanguageServiceDefaultsImpl(defaultLanguageSettings); -monaco.languages.onLanguage(LANGUAGE_ID, () => { - mode.setupMode(kustoDefaults, monaco as typeof monaco); +monaco.languages.onLanguage('kusto', () => { + withMode((mode) => mode.setupMode(kustoDefaults, monaco as typeof globalThis.monaco)); }); monaco.languages.register({ diff --git a/package/src/syntaxHighlighting/SemanticTokensProvider.ts b/package/src/syntaxHighlighting/SemanticTokensProvider.ts index d7b921f3..b3df46b3 100644 --- a/package/src/syntaxHighlighting/SemanticTokensProvider.ts +++ b/package/src/syntaxHighlighting/SemanticTokensProvider.ts @@ -2,7 +2,7 @@ import type * as monaco from 'monaco-editor'; import { editor } from 'monaco-editor'; import { ClassificationRange, DocumentSemanticToken, tokenTypes } from './types'; -type ClassificationsGetter = (uri: string) => Promise; +type ClassificationsGetter = (resource: monaco.Uri) => Promise; export class SemanticTokensProvider implements monaco.languages.DocumentSemanticTokensProvider { private readonly classificationsGetter: ClassificationsGetter; @@ -16,12 +16,13 @@ export class SemanticTokensProvider implements monaco.languages.DocumentSemantic } async provideDocumentSemanticTokens(model: editor.ITextModel) { - const uri = model.uri.toString(); - const classifications = await this.classificationsGetter(uri); + const resource = model.uri; + const classifications = await this.classificationsGetter(resource); const semanticTokens = classifications.map((classification, index) => { const previousClassification = classifications[index - 1]; return semanticTokenMaker(classification, previousClassification); }); + return { data: new Uint32Array(semanticTokens.flat()), resultId: model.getVersionId().toString(), diff --git a/package/src/syntaxHighlighting/semanticTokensProviderRegistrar.ts b/package/src/syntaxHighlighting/semanticTokensProviderRegistrar.ts index cedff7f0..772a345d 100644 --- a/package/src/syntaxHighlighting/semanticTokensProviderRegistrar.ts +++ b/package/src/syntaxHighlighting/semanticTokensProviderRegistrar.ts @@ -1,7 +1,7 @@ import monaco from 'monaco-editor/esm/vs/editor/editor.api'; import { LANGUAGE_ID } from '../globals'; import { SemanticTokensProvider } from './SemanticTokensProvider'; -import { IKustoWorkerImpl } from '../kustoWorker'; +import { AugmentedWorkerAccessor } from '../kustoMode'; export type SemanticTokensProviderRegistrar = ( monacoInstance: typeof monaco, @@ -13,8 +13,8 @@ export type SemanticTokensProviderRegistrar = ( export function semanticTokensProviderRegistrarCreator() { const semanticTokensProviderRegistrar = semanticTokensProviderRegistrarCreatorForTest(); - return (monacoInstance: typeof monaco, worker: IKustoWorkerImpl) => { - const semanticTokensProvider = semanticTokensProviderMaker(worker); + return (monacoInstance: typeof monaco, workerAccessor: AugmentedWorkerAccessor) => { + const semanticTokensProvider = semanticTokensProviderMaker(workerAccessor); semanticTokensProviderRegistrar(monacoInstance, semanticTokensProvider); }; } @@ -33,6 +33,10 @@ export function semanticTokensProviderRegistrarCreatorForTest() { }; } -function semanticTokensProviderMaker(worker: IKustoWorkerImpl): SemanticTokensProvider { - return new SemanticTokensProvider(worker.getClassifications); +function semanticTokensProviderMaker(workerAccessor: AugmentedWorkerAccessor): SemanticTokensProvider { + const classificationsGetter = async (resource: monaco.Uri) => { + const worker = await workerAccessor(resource); + return worker.getClassifications(resource.toString()); + }; + return new SemanticTokensProvider(classificationsGetter); }