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

[vscode] basic support of activation events #5622

Merged
merged 3 commits into from
Jul 4, 2019
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Breaking changes:

- [plugin] fixed typo in 'HostedInstanceState' enum from RUNNNING to RUNNING in `plugin-dev` extension
- [plugin] removed member `processOptions` from `AbstractHostedInstanceManager` as it is not initialized or used
- [plugin] added support of activation events [#5622](https://github.com/theia-ide/theia/pull/5622)
- `HostedPluginSupport` is refactored to support multiple `PluginManagerExt` properly

## v0.8.0

Expand Down
43 changes: 43 additions & 0 deletions packages/core/src/common/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
********************************************************************************/

import { injectable, inject, named } from 'inversify';
import { Event, Emitter } from './event';
import { Disposable, DisposableCollection } from './disposable';
import { ContributionProvider } from './contribution-provider';

Expand Down Expand Up @@ -117,6 +118,19 @@ export interface CommandContribution {
registerCommands(commands: CommandRegistry): void;
}

export interface WillExecuteCommandEvent {
commandId: string;
// tslint:disable:no-any
/**
* Allows to pause the command execution
* in order to register or activate a command handler.
*
* *Note:* It can only be called during event dispatch and not in an asynchronous manner
*/
waitUntil(thenable: Promise<any>): void;
kittaakos marked this conversation as resolved.
Show resolved Hide resolved
// tslint:enable:no-any
}

export const commandServicePath = '/services/commands';
export const CommandService = Symbol('CommandService');
/**
Expand All @@ -130,6 +144,12 @@ export interface CommandService {
*/
// tslint:disable-next-line:no-any
executeCommand<T>(command: string, ...args: any[]): Promise<T | undefined>;
/**
* An event is emmited when a command is about to be executed.
*
* It can be used to install or activate a command handler.
*/
readonly onWillExecuteCommand: Event<WillExecuteCommandEvent>;
}

/**
Expand All @@ -144,6 +164,9 @@ export class CommandRegistry implements CommandService {
// List of recently used commands.
protected _recent: Command[] = [];

protected readonly onWillExecuteCommandEmitter = new Emitter<WillExecuteCommandEvent>();
readonly onWillExecuteCommand = this.onWillExecuteCommandEmitter.event;

constructor(
@inject(ContributionProvider) @named(CommandContribution)
protected readonly contributionProvider: ContributionProvider<CommandContribution>
Expand Down Expand Up @@ -255,6 +278,7 @@ export class CommandRegistry implements CommandService {
*/
// tslint:disable-next-line:no-any
async executeCommand<T>(commandId: string, ...args: any[]): Promise<T | undefined> {
await this.fireWillExecuteCommand(commandId);
kittaakos marked this conversation as resolved.
Show resolved Hide resolved
const handler = this.getActiveHandler(commandId, ...args);
if (handler) {
const result = await handler.execute(...args);
Expand All @@ -268,6 +292,25 @@ export class CommandRegistry implements CommandService {
throw new Error(`The command '${commandId}' cannot be executed. There are no active handlers available for the command.${argsMessage}`);
}

protected async fireWillExecuteCommand(commandId: string): Promise<void> {
const waitables: Promise<void>[] = [];
this.onWillExecuteCommandEmitter.fire({
commandId,
waitUntil: (thenable: Promise<void>) => {
if (Object.isFrozen(waitables)) {
throw new Error('waitUntil cannot be called asynchronously.');
}
waitables.push(thenable);
}
});
if (!waitables.length) {
return;
}
// Asynchronous calls to `waitUntil` should fail.
Object.freeze(waitables);
await Promise.race([Promise.all(waitables), new Promise(resolve => setTimeout(resolve, 30000))]);
kittaakos marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Get a visible handler for the given command or `undefined`.
*/
Expand Down
54 changes: 33 additions & 21 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 } from '@theia/core';
import { ILogger, ContributionProvider, Emitter } 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 @@ -30,6 +30,14 @@ export type OnigasmPromise = Promise<IOnigLib>;
@injectable()
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;

@inject(ContributionProvider) @named(LanguageGrammarDefinitionContribution)
Expand Down Expand Up @@ -97,35 +105,39 @@ export class MonacoTextmateService implements FrontendApplicationContribution {
}
});

const registered = new Set<string>();
for (const { id } of monaco.languages.getLanguages()) {
if (!registered.has(id)) {
monaco.languages.onLanguage(id, () => this.activateLanguage(id));
registered.add(id);
}
monaco.languages.onLanguage(id, () => this.activateLanguage(id));
}
}

async activateLanguage(languageId: string) {
const scopeName = this.textmateRegistry.getScope(languageId);
if (!scopeName) {
return;
}
const provider = this.textmateRegistry.getProvider(scopeName);
if (!provider) {
if (this._activatedLanguages.has(languageId)) {
return;
}
this._activatedLanguages.add(languageId);
try {
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);
const configuration = this.textmateRegistry.getGrammarConfiguration(languageId);
const initialLanguage = getEncodedLanguageId(languageId);

await this.onigasmPromise;
try {
const grammar = await this.grammarRegistry.loadGrammarWithConfiguration(scopeName, initialLanguage, configuration);
const options = configuration.tokenizerOption ? configuration.tokenizerOption : TokenizerOption.DEFAULT;
monaco.languages.setTokensProvider(languageId, createTextmateTokenizer(grammar, options));
} catch (error) {
this.logger.warn('No grammar for this language id', languageId, error);
await this.onigasmPromise;
try {
const grammar = await this.grammarRegistry.loadGrammarWithConfiguration(scopeName, initialLanguage, configuration);
const options = configuration.tokenizerOption ? configuration.tokenizerOption : TokenizerOption.DEFAULT;
monaco.languages.setTokensProvider(languageId, createTextmateTokenizer(grammar, options));
} catch (error) {
this.logger.warn('No grammar for this language id', languageId, error);
}
} finally {
this.onDidActivateLanguageEmitter.fire(languageId);
}
}
}
10 changes: 7 additions & 3 deletions packages/plugin-ext-vscode/src/node/plugin-vscode-init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,14 @@ export const doInitialization: BackendInitializationFn = (apiFactory: PluginAPIF

// replace command API as it will send only the ID as a string parameter
const registerCommand = vscode.commands.registerCommand;
vscode.commands.registerCommand = function (command: any, handler?: <T>(...args: any[]) => T | Thenable<T>, thisArg?: any): any {
vscode.commands.registerCommand = function (command: theia.CommandDescription | string, handler?: <T>(...args: any[]) => T | Thenable<T>, thisArg?: any): any {
// use of the ID when registering commands
if (typeof command === 'string' && handler) {
return vscode.commands.registerHandler(command, handler, thisArg);
if (typeof command === 'string') {
const commands = plugin.model.contributes && plugin.model.contributes.commands;
if (handler && commands && commands.some(item => item.command === command)) {
return vscode.commands.registerHandler(command, handler, thisArg);
}
return registerCommand({ id: command }, handler, thisArg);
}
return registerCommand(command, handler, thisArg);
};
Expand Down
3 changes: 3 additions & 0 deletions packages/plugin-ext/src/api/plugin-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export interface PluginInitData {
workspaceState: KeysToKeysToAnyValue;
env: EnvInit;
extApi?: ExtPluginApi[];
activationEvents: string[]
}

export interface PreferenceData {
Expand Down Expand Up @@ -163,6 +164,8 @@ export interface PluginManagerExt {
$init(pluginInit: PluginInitData, configStorage: ConfigStorage): PromiseLike<void>;

$updateStoragePath(path: string | undefined): PromiseLike<void>;

$activateByEvent(event: string): Promise<void>;
kittaakos marked this conversation as resolved.
Show resolved Hide resolved
}

export interface CommandRegistryMain {
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-ext/src/common/plugin-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export interface PluginPackage {
description: string;
contributes?: PluginPackageContribution;
packagePath: string;
activationEvents?: string[];
}
export namespace PluginPackage {
export function toPluginUrl(pck: PluginPackage, relativePath: string): string {
Expand Down
Loading