Skip to content

Commit

Permalink
Map well known commands to theia cmd
Browse files Browse the repository at this point in the history
Signed-off-by: Thomas Mäder <tmader@redhat.com>
  • Loading branch information
tsmaeder committed Feb 15, 2019
1 parent 891a4e3 commit c5b598e
Show file tree
Hide file tree
Showing 10 changed files with 112 additions and 139 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
- [plugin] added `tasks.onDidEndTask` Plug-in API
- [cpp] fixed `CPP_CLANGD_COMMAND` and `CPP_CLANGD_ARGS` environment variables
- [electron] open markdown links in the OS default browser
- [plugin] the "Command" interface has been split into two: "CommandDescription" and "Command". "Command" has been
made compatible with the "Command" interface in vscode. This is not a breaking change, currently, but fields in those interfaces
have been deprecated and will be removed in the future.

Breaking changes:
- menus aligned with built-in VS Code menus [#4173](https://github.com/theia-ide/theia/pull/4173)
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-ext/src/api/plugin-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ export interface PluginManagerExt {
}

export interface CommandRegistryMain {
$registerCommand(command: theia.Command): void;
$registerCommand(command: theia.CommandDescription): void;
$unregisterCommand(id: string): void;

$registerHandler(id: string): void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export class CommandRegistryMainImpl implements CommandRegistryMain {
this.keyBinding = container.get(KeybindingRegistry);
}

$registerCommand(command: theia.Command): void {
$registerCommand(command: theia.CommandDescription): void {
this.commands.set(command.id, this.delegate.registerCommand(command));
}
$unregisterCommand(id: string): void {
Expand Down
117 changes: 7 additions & 110 deletions packages/plugin-ext/src/plugin/command-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ import * as theia from '@theia/plugin';
import { CommandRegistryExt, PLUGIN_RPC_CONTEXT as Ext, CommandRegistryMain } from '../api/plugin-api';
import { RPCProtocol } from '../api/rpc-protocol';
import { Disposable } from './types-impl';
import { Command } from '../api/model';
import { ObjectIdentifier } from '../common/object-identifier';
import { KnownCommands } from './type-converters';

// tslint:disable-next-line:no-any
export type Handler = <T>(...args: any[]) => T | PromiseLike<T>;
Expand All @@ -29,9 +28,6 @@ export class CommandRegistryImpl implements CommandRegistryExt {
private proxy: CommandRegistryMain;
private readonly commands = new Set<string>();
private readonly handlers = new Map<string, Handler>();
private converter: CommandsConverter;
private cache = new Map<number, theia.Command>();
private delegatingCommandId: string;

// tslint:disable-next-line:no-any
private static EMPTY_HANDLER(...args: any[]): Promise<any> { return Promise.resolve(undefined); }
Expand All @@ -43,22 +39,8 @@ export class CommandRegistryImpl implements CommandRegistryExt {
this.registerCommand({ id: 'vscode.previewHtml' }, CommandRegistryImpl.EMPTY_HANDLER);
}

getConverter(): CommandsConverter {
if (this.converter) {
return this.converter;
} else {
this.delegatingCommandId = `_internal_command_delegation_${Date.now()}`;
const command: theia.Command = {
id: this.delegatingCommandId
};
this.registerCommand(command, this.executeConvertedCommand);
this.converter = new CommandsConverter(this.delegatingCommandId, this.cache);
return this.converter;
}
}

// tslint:disable-next-line:no-any
registerCommand(command: theia.Command, handler?: Handler, thisArg?: any): Disposable {
registerCommand(command: theia.CommandDescription, handler?: Handler, thisArg?: any): Disposable {
if (this.commands.has(command.id)) {
throw new Error(`Command ${command.id} already exist`);
}
Expand Down Expand Up @@ -103,14 +85,16 @@ export class CommandRegistryImpl implements CommandRegistryExt {
}
}

// tslint:disable-next-line:no-any
// tslint:disable:no-any
executeCommand<T>(id: string, ...args: any[]): PromiseLike<T | undefined> {
if (this.handlers.has(id)) {
return this.executeLocalCommand(id, ...args);
} else {
return this.proxy.$executeCommand(id, ...args);
return KnownCommands.map(id, args, (mappedId: string, mappedArgs: any[] | undefined) =>
this.proxy.$executeCommand(mappedId, ...mappedArgs));
}
}
// tslint:enable:no-any

getKeyBinding(commandId: string): PromiseLike<theia.CommandKeyBinding[] | undefined> {
return this.proxy.$getKeyBinding(commandId);
Expand All @@ -120,24 +104,12 @@ export class CommandRegistryImpl implements CommandRegistryExt {
private executeLocalCommand<T>(id: string, ...args: any[]): PromiseLike<T> {
const handler = this.handlers.get(id);
if (handler) {
const result = id === this.delegatingCommandId ?
handler(this, ...args)
: handler.apply(undefined, args);
return Promise.resolve(result);
return Promise.resolve(handler(...args));
} else {
return Promise.reject(new Error(`Command ${id} doesn't exist`));
}
}

// tslint:disable-next-line:no-any
executeConvertedCommand(commands: CommandRegistryImpl, ...args: any[]): PromiseLike<any> {
const actualCmd = commands.cache.get(args[0]);
if (!actualCmd) {
return Promise.resolve(undefined);
}
return commands.executeCommand(actualCmd.command ? actualCmd.command : actualCmd.id, ...(actualCmd.arguments || []));
}

async getCommands(filterUnderscoreCommands: boolean = false): Promise<string[]> {
const result = await this.proxy.$getCommands();
if (filterUnderscoreCommands) {
Expand All @@ -146,78 +118,3 @@ export class CommandRegistryImpl implements CommandRegistryExt {
return result;
}
}

/** Converter between internal and api commands. */
export class CommandsConverter {
private readonly delegatingCommandId: string;
private cacheId = 0;
private cache: Map<number, theia.Command>;

constructor(id: string, cache: Map<number, theia.Command>) {
this.cache = cache;
this.delegatingCommandId = id;
}

toInternal(command: theia.Command | undefined): Command | undefined {
if (!command) {
return undefined;
}

let title;
if (command.title) {
title = command.title;
} else if (command.label) {
title = command.label;
} else {
return undefined;
}

const result: Command = {
id: command.command ? command.command : command.id,
title: title
};

if (command.command && !CommandsConverter.isFalsyOrEmpty(command.arguments)) {
const id = this.cacheId++;
ObjectIdentifier.mixin(result, id);
this.cache.set(id, command);

result.id = this.delegatingCommandId;
result.arguments = [id];
}

if (command.tooltip) {
result.tooltip = command.tooltip;
}

return result;
}

fromInternal(command: Command | undefined): theia.Command | undefined {
if (!command) {
return undefined;
}

const id = ObjectIdentifier.of(command);
if (typeof id === 'number') {
return this.cache.get(id);
} else {
return {
id: command.id,
label: command.title,
command: command.id,
title: command.title,
arguments: command.arguments
};
}
}

/**
* @returns `false` if the provided object is an array and not empty.
*/
// tslint:disable-next-line:no-any
private static isFalsyOrEmpty(obj: any): boolean {
// tslint:disable-next-line:no-any
return !Array.isArray(obj) || (<Array<any>>obj).length === 0;
}
}
4 changes: 2 additions & 2 deletions packages/plugin-ext/src/plugin/languages/code-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export class CodeActionAdapter {
}
if (CodeActionAdapter._isCommand(candidate)) {
result.push({
title: candidate.label || '',
title: candidate.title || '',
command: Converter.toInternalCommand(candidate)
});
} else {
Expand Down Expand Up @@ -98,7 +98,7 @@ export class CodeActionAdapter {

// tslint:disable-next-line:no-any
private static _isCommand(smth: any): smth is theia.Command {
return typeof (<theia.Command>smth).id === 'string';
return typeof (<theia.Command>smth).command === 'string' || typeof (<theia.Command>smth).id === 'string';
}

// tslint:disable-next-line:no-any
Expand Down
3 changes: 1 addition & 2 deletions packages/plugin-ext/src/plugin/languages/lens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { createToken } from '../token-provider';
/** Adapts the calls from main to extension thread for providing/resolving the code lenses. */
export class CodeLensAdapter {

private static readonly BAD_CMD: theia.Command = { id: 'missing', label: '<<MISSING COMMAND>>' };
private static readonly BAD_CMD: theia.Command = { command: 'missing', title: '<<MISSING COMMAND>>' };

private cacheId = 0;
private cache = new Map<number, theia.CodeLens>();
Expand All @@ -47,7 +47,6 @@ export class CodeLensAdapter {
if (Array.isArray(lenses)) {
return lenses.map(lens => {
const id = this.cacheId++;
console.log(lens);
const lensSymbol = ObjectIdentifier.mixin({
range: Converter.fromRange(lens.range)!,
command: lens.command ? Converter.toInternalCommand(lens.command) : undefined
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-ext/src/plugin/plugin-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ export function createAPIFactory(
return function (plugin: InternalPlugin): typeof theia {
const commands: typeof theia.commands = {
// tslint:disable-next-line:no-any
registerCommand(command: theia.Command, handler?: <T>(...args: any[]) => T | Thenable<T>, thisArg?: any): Disposable {
registerCommand(command: theia.CommandDescription, handler?: <T>(...args: any[]) => T | Thenable<T>, thisArg?: any): Disposable {
return commandRegistry.registerCommand(command, handler, thisArg);
},
// tslint:disable-next-line:no-any
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-ext/src/plugin/tree/tree-views.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ class TreeViewExtImpl<T> extends Disposable {
const treeItem = await this.treeDataProvider.getTreeItem(cachedElement);

if (treeItem.command) {
this.commandRegistry.executeCommand(treeItem.command.id, ...(treeItem.command.arguments || []));
this.commandRegistry.executeCommand((treeItem.command.command || treeItem.command.id)!, ...(treeItem.command.arguments || []));
}
}
}
Expand Down
72 changes: 65 additions & 7 deletions packages/plugin-ext/src/plugin/type-converters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import URI from 'vscode-uri';

const SIDE_GROUP = -2;
const ACTIVE_GROUP = -1;
import { SymbolInformation, Range as R, Position as P, SymbolKind as S } from 'vscode-languageserver-types';
import { SymbolInformation, Range as R, Position as P, SymbolKind as S, Location as L } from 'vscode-languageserver-types';

export function toViewColumn(ep?: EditorPosition): theia.ViewColumn | undefined {
if (typeof ep !== 'number') {
Expand Down Expand Up @@ -401,12 +401,70 @@ export function fromDocumentHighlight(documentHighlight: theia.DocumentHighlight
};
}

export function toInternalCommand(command: theia.Command): model.Command {
return {
id: command.command ? command.command : command.id,
title: command.title ? command.title : command.label || ' ',
tooltip: command.tooltip,
arguments: command.arguments
export function toInternalCommand(external: theia.Command): model.Command {
// we're deprecating Command.id, so it has to be optional.
// Existing code will have compiled against a non - optional version of the field, so asserting it to exist is ok
// tslint:disable-next-line: no-any
return KnownCommands.map((external.command || external.id)!, external.arguments, (mappedId: string, mappedArgs: any[]) =>
({
id: mappedId,
title: external.title || external.label || ' ',
tooltip: external.tooltip,
arguments: mappedArgs
}));
}

export namespace KnownCommands {
// tslint:disable: no-any
const mappings: { [id: string]: [string, (args: any[] | undefined) => any[] | undefined] } = {};
mappings['editor.action.showReferences'] = ['textEditor.commands.showReferences', createConversionFunction(
(uri: URI) => uri.toString(),
fromPositionToP,
toArrayConversion(fromLocationToL))];

export function map<T>(id: string, args: any[] | undefined, toDo: (mappedId: string, mappedArgs: any[] | undefined) => T): T {
if (mappings[id]) {
return toDo(mappings[id][0], mappings[id][1](args));
} else {
return toDo(id, args);
}
}

type conversionFunction = ((parameter: any) => any) | undefined;
function createConversionFunction(...conversions: conversionFunction[]): (args: any[] | undefined) => any[] | undefined {
return function (args: any[] | undefined): any[] | undefined {
if (!args) {
return args;
}
return args.map(function (arg: any, index: number): any {
if (index < conversions.length) {
const conversion = conversions[index];
if (conversion) {
return conversion(arg);
}
}
return arg;
});
};
}
// tslint:enable: no-any
function fromPositionToP(p: theia.Position): P {
return P.create(p.line, p.character);
}

function fromRangeToR(r: theia.Range): R {
return R.create(fromPositionToP(r.start), fromPositionToP(r.end));
}

function fromLocationToL(l: theia.Location): L {
return L.create(l.uri.toString(), fromRangeToR(l.range));
}

}

function toArrayConversion<T, U>(f: (a: T) => U): (a: T[]) => U[] {
return function (a: T[]) {
return a.map(f);
};
}

Expand Down
Loading

0 comments on commit c5b598e

Please sign in to comment.