Skip to content

Commit

Permalink
[textmate]: activate grammar only for languages with registered grammars
Browse files Browse the repository at this point in the history
Before we will activate grammars for all languages which is bogus since grammar is not necessary registered yet. Now we will trigger grammar activation only if it was registered.

Signed-off-by: Anton Kosyakov <anton.kosyakov@typefox.io>
  • Loading branch information
akosyakov committed Feb 12, 2020
1 parent 4b3f78e commit 00535be
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 53 deletions.
92 changes: 56 additions & 36 deletions packages/monaco/src/browser/textmate/monaco-textmate-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import { injectable, inject, named } from 'inversify';
import { Registry, IOnigLib, IRawGrammar, parseRawGrammar } from 'vscode-textmate';
import { ILogger, ContributionProvider, Emitter, DisposableCollection, Disposable } from '@theia/core';
import { ILogger, ContributionProvider, DisposableCollection, Disposable } from '@theia/core';
import { FrontendApplicationContribution, isBasicWasmSupported } from '@theia/core/lib/browser';
import { ThemeService } from '@theia/core/lib/browser/theming';
import { LanguageGrammarDefinitionContribution, getEncodedLanguageId } from './textmate-contribution';
Expand All @@ -31,12 +31,6 @@ export type OnigasmPromise = Promise<IOnigLib>;
export class MonacoTextmateService implements FrontendApplicationContribution {

protected readonly _activatedLanguages = new Set<string>();
get activatedLanguages(): ReadonlySet<string> {
return this._activatedLanguages;
}

protected readonly onDidActivateLanguageEmitter = new Emitter<string>();
readonly onDidActivateLanguage = this.onDidActivateLanguageEmitter.event;

protected grammarRegistry: Registry;

Expand Down Expand Up @@ -101,8 +95,8 @@ export class MonacoTextmateService implements FrontendApplicationContribution {
this.updateTheme();
this.themeService.onThemeChange(() => this.updateTheme());

for (const { id } of monaco.languages.getLanguages()) {
monaco.languages.onLanguage(id, () => this.activateLanguage(id));
for (const id of this.textmateRegistry.languages) {
this.activateLanguage(id);
}
}

Expand All @@ -129,44 +123,70 @@ export class MonacoTextmateService implements FrontendApplicationContribution {
return this.themeService.getCurrentTheme().editorTheme || MonacoThemeRegistry.DARK_DEFAULT_THEME;
}

async activateLanguage(languageId: string): Promise<void> {
activateLanguage(language: string): Disposable {
const toDispose = new DisposableCollection(
Disposable.create(() => { /* mark as not disposed */ })
);
toDispose.push(this.waitForLanguage(language, () =>
this.doActivateLanguage(language, toDispose)
));
return toDispose;
}

protected async doActivateLanguage(languageId: string, toDispose: DisposableCollection): Promise<void> {
if (this._activatedLanguages.has(languageId)) {
return;
}
this._activatedLanguages.add(languageId);
toDispose.push(Disposable.create(() => this._activatedLanguages.delete(languageId)));

const scopeName = this.textmateRegistry.getScope(languageId);
if (!scopeName) {
return;
}
const provider = this.textmateRegistry.getProvider(scopeName);
if (!provider) {
return;
}

const configuration = this.textmateRegistry.getGrammarConfiguration(languageId);
const initialLanguage = getEncodedLanguageId(languageId);

await this.onigasmPromise;
if (toDispose.disposed) {
return;
}
try {
const scopeName = this.textmateRegistry.getScope(languageId);
if (!scopeName) {
const grammar = await this.grammarRegistry.loadGrammarWithConfiguration(scopeName, initialLanguage, configuration);
if (toDispose.disposed) {
return;
}
const provider = this.textmateRegistry.getProvider(scopeName);
if (!provider) {
return;
if (!grammar) {
throw new Error(`no grammar for ${scopeName}, ${initialLanguage}, ${JSON.stringify(configuration)}`);
}
const options = configuration.tokenizerOption ? configuration.tokenizerOption : TokenizerOption.DEFAULT;
const tokenizer = createTextmateTokenizer(grammar, options);
toDispose.push(monaco.languages.setTokensProvider(languageId, tokenizer));
const support = monaco.modes.TokenizationRegistry.get(languageId);
const themeService = monaco.services.StaticServices.standaloneThemeService.get();
const languageIdentifier = monaco.services.StaticServices.modeService.get().getLanguageIdentifier(languageId);
const adapter = new monaco.services.TokenizationSupport2Adapter(themeService, languageIdentifier!, tokenizer);
support!.tokenize = adapter.tokenize.bind(adapter);
} catch (error) {
this.logger.warn('No grammar for this language id', languageId, error);
}
}

const configuration = this.textmateRegistry.getGrammarConfiguration(languageId);
const initialLanguage = getEncodedLanguageId(languageId);

await this.onigasmPromise;
try {
const grammar = await this.grammarRegistry.loadGrammarWithConfiguration(scopeName, initialLanguage, configuration);
if (!grammar) {
throw new Error(`no grammar for ${scopeName}, ${initialLanguage}, ${JSON.stringify(configuration)}`);
}
const options = configuration.tokenizerOption ? configuration.tokenizerOption : TokenizerOption.DEFAULT;
const tokenizer = createTextmateTokenizer(grammar, options);
monaco.languages.setTokensProvider(languageId, tokenizer);
const support = monaco.modes.TokenizationRegistry.get(languageId);
const themeService = monaco.services.StaticServices.standaloneThemeService.get();
const languageIdentifier = monaco.services.StaticServices.modeService.get().getLanguageIdentifier(languageId);
const adapter = new monaco.services.TokenizationSupport2Adapter(themeService, languageIdentifier!, tokenizer);
support!.tokenize = adapter.tokenize.bind(adapter);
} catch (error) {
this.logger.warn('No grammar for this language id', languageId, error);
protected waitForLanguage(language: string, cb: () => {}): Disposable {
const modeService = monaco.services.StaticServices.modeService.get();
for (const modeId of Object.keys(modeService['_instantiatedModes'])) {
const mode = modeService['_instantiatedModes'][modeId];
if (mode.getId() === language) {
cb();
return Disposable.NULL;
}
} finally {
this.onDidActivateLanguageEmitter.fire(languageId);
}
return monaco.languages.onLanguage(language, cb);
}

}
4 changes: 4 additions & 0 deletions packages/monaco/src/browser/textmate/textmate-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ export class TextmateRegistry {
protected readonly languageToConfig = new Map<string, TextmateGrammarConfiguration[]>();
protected readonly languageIdToScope = new Map<string, string[]>();

get languages(): IterableIterator<string> {
return this.languageIdToScope.keys();
}

registerTextmateGrammarScope(scope: string, provider: GrammarDefinitionProvider): Disposable {
const providers = this.scopeToProvider.get(scope) || [];
const existingProvider = providers[0];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,6 @@ export class PluginContributionHandler {
protected readonly onDidRegisterCommandHandlerEmitter = new Emitter<string>();
readonly onDidRegisterCommandHandler = this.onDidRegisterCommandHandlerEmitter.event;

protected readonly activatedLanguages = new Set<string>();

/**
* Always synchronous in order to simplify handling disconnections.
* @throws never, loading of each contribution should handle errors
Expand Down Expand Up @@ -137,12 +135,6 @@ export class PluginContributionHandler {
const languages = contributions.languages;
if (languages && languages.length) {
for (const lang of languages) {
/*
* Monaco guesses a language for opened plain text models on `monaco.languages.register`.
* It can trigger language activation before grammars are registered.
* Install onLanguage listener earlier in order to catch such activations and activate grammars as well.
*/
monaco.languages.onLanguage(lang.id, () => this.activatedLanguages.add(lang.id));
// it is not possible to unregister a language
monaco.languages.register({
id: lang.id,
Expand Down Expand Up @@ -209,7 +201,7 @@ export class PluginContributionHandler {
tokenTypes: this.convertTokenTypes(grammar.tokenTypes)
}));
pushContribution(`grammar.language.${language}.activation`,
() => this.onDidActivateLanguage(language, () => this.monacoTextmateService.activateLanguage(language))
() => this.monacoTextmateService.activateLanguage(language)
);
}
});
Expand Down Expand Up @@ -356,14 +348,6 @@ export class PluginContributionHandler {
return !!this.commandHandlers.get(id);
}

protected onDidActivateLanguage(language: string, cb: () => {}): Disposable {
if (this.activatedLanguages.has(language)) {
cb();
return Disposable.NULL;
}
return monaco.languages.onLanguage(language, cb);
}

protected updateDefaultOverridesSchema(configurationDefaults: PreferenceSchemaProperties): Disposable {
const defaultOverrides: PreferenceSchema = {
id: 'defaultOverrides',
Expand Down

0 comments on commit 00535be

Please sign in to comment.