Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Revert rafactor to isolate build and tests issues #475

Merged
merged 4 commits into from
Jul 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion package/package.json
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
181 changes: 114 additions & 67 deletions package/src/kustoMode.ts
Original file line number Diff line number Diff line change
@@ -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<IKustoWorkerImpl, 'setSchemaFromShowSchema' | 'getReferencedSymbols'> {}

export interface AugmentedWorkerAccessor {
(first: monaco.Uri): Promise<AugmentedWorker>;
(first: monaco.Uri, ...more: monaco.Uri[]): Promise<AugmentedWorker>;
}

let workerAccessor: AugmentedWorkerAccessor;

export async function setupMode(
let kustoWorker: AugmentedWorkerAccessor;
let resolveWorker: (value: AugmentedWorkerAccessor | PromiseLike<AugmentedWorkerAccessor>) => void;
let rejectWorker: (err: any) => void;
let workerPromise: Promise<AugmentedWorkerAccessor> = 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<AugmentedWorkerAccessor> {
monacoInstance: typeof globalThis.monaco
): AugmentedWorkerAccessor {
let onSchemaChange = new monaco.Emitter<Schema>();
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 },
Expand All @@ -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<AugmentedWorkerAccessor> {
return workerAccessor;
export function getKustoWorker(): Promise<AugmentedWorkerAccessor> {
return workerPromise.then(() => kustoWorker);
}
2 changes: 1 addition & 1 deletion package/src/languageFeatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions package/src/languageServiceManager/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
20 changes: 15 additions & 5 deletions package/src/monaco.contribution.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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<LanguageServiceDefaults>();
private _languageSettings: LanguageSettings;
Expand Down Expand Up @@ -78,14 +80,22 @@ const defaultLanguageSettings: LanguageSettings = {
completionOptions: { includeExtendedSyntax: false },
};

export async function getKustoWorker(): Promise<WorkerAccessor> {
return mode.getKustoWorker();
export function getKustoWorker(): Promise<WorkerAccessor> {
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({
Expand Down
7 changes: 4 additions & 3 deletions package/src/syntaxHighlighting/SemanticTokensProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ClassificationRange[]>;
type ClassificationsGetter = (resource: monaco.Uri) => Promise<ClassificationRange[]>;

export class SemanticTokensProvider implements monaco.languages.DocumentSemanticTokensProvider {
private readonly classificationsGetter: ClassificationsGetter;
Expand All @@ -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(),
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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);
};
}
Expand All @@ -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);
}
Loading