Skip to content

Commit

Permalink
[monaco] fix eclipse-theia#6920: handle internal open calls with Open…
Browse files Browse the repository at this point in the history
…erService

Signed-off-by: Anton Kosyakov <anton.kosyakov@typefox.io>
  • Loading branch information
akosyakov committed Jan 23, 2020
1 parent 2611731 commit 9af5ee3
Show file tree
Hide file tree
Showing 10 changed files with 132 additions and 30 deletions.
24 changes: 24 additions & 0 deletions examples/api-tests/src/monaco-api.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ describe('Monaco API', async function () {
const { WorkspaceService } = require('@theia/workspace/lib/browser/workspace-service');
const { MonacoEditor } = require('@theia/monaco/lib/browser/monaco-editor');
const { MonacoResolvedKeybinding } = require('@theia/monaco/lib/browser/monaco-resolved-keybinding');
const { CommandRegistry } = require('@theia/core/lib/common/command');

/** @type {import('inversify').Container} */
const container = window['theia'].container;
const editorManager = container.get(EditorManager);
const workspaceService = container.get(WorkspaceService);
const commands = container.get(CommandRegistry);

/** @type {MonacoEditor} */
let monacoEditor;
Expand Down Expand Up @@ -87,4 +89,26 @@ describe('Monaco API', async function () {
}
});

it('OpenerService.open', async () => {
const hoverContribution = monacoEditor.getControl().getContribution('editor.contrib.hover');
assert.isDefined(hoverContribution);
if (!('_openerService' in hoverContribution)) {
assert.fail(`hoverContribution does not have OpenerService`);
}
/** @type {monaco.services.OpenerService} */
const openerService = hoverContribution['_openerService'];

let opened = false;
const id = '__test:OpenerService.open'
const unregisterCommand = commands.registerCommand({ id }, {
execute: () => opened = true
});
try {
await openerService.open(monaco.Uri.parse('command:' + id));
assert.isTrue(opened);
} finally {
unregisterCommand.dispose();
}
});

});
Original file line number Diff line number Diff line change
Expand Up @@ -15,33 +15,34 @@
********************************************************************************/

import { injectable, inject } from 'inversify';
import URI from '@theia/core/lib/common/uri';
import { OpenHandler } from '@theia/core/lib/browser/opener-service';
import { Schemes } from '../../common/uri-components';
import { CommandService } from '@theia/core/lib/common/command';
import { CommandService } from '../common/command';
import URI from '../common/uri';
import { OpenHandler } from './opener-service';

@injectable()
export class PluginCommandOpenHandler implements OpenHandler {
export class CommandOpenHandler implements OpenHandler {

readonly id = 'plugin-command';
readonly id = 'command';

@inject(CommandService)
protected readonly commands: CommandService;

canHandle(uri: URI): number {
return uri.scheme === Schemes.COMMAND ? 500 : -1;
return uri.scheme === 'command' ? 500 : -1;
}

async open(uri: URI): Promise<boolean> {
// tslint:disable-next-line:no-any
let args: any = [];
try {
args = JSON.parse(uri.query);
if (!Array.isArray(args)) {
args = [args];
args = JSON.parse(decodeURIComponent(uri.query));
} catch {
// ignore and retry
try {
args = JSON.parse(uri.query);
} catch {
// ignore error
}
} catch (e) {
// ignore error
}
await this.commands.executeCommand(uri.path.toString(), ...args);
return true;
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/browser/frontend-application-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ import { ExternalUriService } from './external-uri-service';
import { IconThemeService, NoneIconTheme } from './icon-theme-service';
import { IconThemeApplicationContribution, IconThemeContribution, DefaultFileIconThemeContribution } from './icon-theme-contribution';
import { TreeLabelProvider } from './tree/tree-label-provider';
import { CommandOpenHandler } from './command-open-handler';

export { bindResourceProvider, bindMessageService, bindPreferenceService };

Expand Down Expand Up @@ -159,6 +160,9 @@ export const frontendApplicationModule = new ContainerModule((bind, unbind, isBo
bind(HttpOpenHandler).toSelf().inSingletonScope();
bind(OpenHandler).toService(HttpOpenHandler);

bind(CommandOpenHandler).toSelf().inSingletonScope();
bind(OpenHandler).toService(CommandOpenHandler);

bindContributionProvider(bind, ApplicationShellLayoutMigration);
bind<ApplicationShellLayoutMigration>(ApplicationShellLayoutMigration).toConstantValue({
layoutVersion: 2.0,
Expand Down
8 changes: 6 additions & 2 deletions packages/core/src/browser/http-open-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ import { OpenHandler } from './opener-service';
import { WindowService } from './window/window-service';
import { ExternalUriService } from './external-uri-service';

export interface HttpOpenHandlerOptions {
openExternal?: boolean
}

@injectable()
export class HttpOpenHandler implements OpenHandler {

Expand All @@ -31,8 +35,8 @@ export class HttpOpenHandler implements OpenHandler {
@inject(ExternalUriService)
protected readonly externalUriService: ExternalUriService;

canHandle(uri: URI): number {
return (uri.scheme.startsWith('http') || uri.scheme.startsWith('mailto')) ? 500 : 0;
canHandle(uri: URI, options?: HttpOpenHandlerOptions): number {
return ((options && options.openExternal) || uri.scheme.startsWith('http') || uri.scheme.startsWith('mailto')) ? 500 : 0;
}

async open(uri: URI): Promise<undefined> {
Expand Down
22 changes: 18 additions & 4 deletions packages/editor/src/browser/editor-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,14 +103,28 @@ export class EditorManager extends NavigatableWidgetOpenHandler<EditorWidget> {

async open(uri: URI, options?: EditorOpenerOptions): Promise<EditorWidget> {
const editor = await super.open(uri, options);
this.revealSelection(editor, options);
this.revealSelection(editor, options, uri);
return editor;
}

protected revealSelection(widget: EditorWidget, input?: EditorOpenerOptions): void {
if (input && input.selection) {
protected revealSelection(widget: EditorWidget, input?: EditorOpenerOptions, uri?: URI): void {
let inputSelection = input && input.selection;
if (!inputSelection && uri) {
const match = /^L?(\d+)(?:,(\d+))?/.exec(uri.fragment);
if (match) {
// support file:///some/file.js#73,84
// support file:///some/file.js#L73
inputSelection = {
start: {
line: parseInt(match[1]) - 1,
character: match[2] ? parseInt(match[2]) - 1 : 0
}
};
}
}
if (inputSelection) {
const editor = widget.editor;
const selection = this.getSelection(widget, input.selection);
const selection = this.getSelection(widget, inputSelection);
if (Position.is(selection)) {
editor.cursor = selection;
editor.revealPosition(selection);
Expand Down
44 changes: 41 additions & 3 deletions packages/monaco/src/browser/monaco-editor-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ import { MonacoBulkEditService } from './monaco-bulk-edit-service';
import IEditorOverrideServices = monaco.editor.IEditorOverrideServices;
import { ApplicationServer } from '@theia/core/lib/common/application-protocol';
import { OS } from '@theia/core';
import { KeybindingRegistry } from '@theia/core/lib/browser';
import { KeybindingRegistry, OpenerService, open, WidgetOpenerOptions } from '@theia/core/lib/browser';
import { MonacoResolvedKeybinding } from './monaco-resolved-keybinding';
import { HttpOpenHandlerOptions } from '@theia/core/lib/browser/http-open-handler';

@injectable()
export class MonacoEditorProvider {
Expand All @@ -49,7 +50,10 @@ export class MonacoEditorProvider {
protected readonly services: MonacoEditorServices;

@inject(KeybindingRegistry)
protected keybindingRegistry: KeybindingRegistry;
protected readonly keybindingRegistry: KeybindingRegistry;

@inject(OpenerService)
protected readonly openerService: OpenerService;

private isWindowsBackend: boolean = false;

Expand Down Expand Up @@ -119,14 +123,19 @@ export class MonacoEditorProvider {
const contextKeyService = this.contextKeyService.createScoped();
const { codeEditorService, textModelService, contextMenuService } = this;
const IWorkspaceEditService = this.bulkEditService;
const openerService = new monaco.services.OpenerService(codeEditorService, commandService);
openerService.registerOpener({
open: (uri, options) => this.interceptOpen(uri, options)
});
const toDispose = new DisposableCollection();
const editor = await factory({
codeEditorService,
textModelService,
contextMenuService,
commandService,
IWorkspaceEditService,
contextKeyService
contextKeyService,
openerService
}, toDispose);
editor.onDispose(() => toDispose.dispose());

Expand All @@ -152,6 +161,35 @@ export class MonacoEditorProvider {
return editor;
}

/**
* Intercept internal Monaco open calls and delegate to OpenerService.
*/
protected async interceptOpen(monacoUri: monaco.Uri | string, monacoOptions?: monaco.services.OpenInternalOptions | monaco.services.OpenExternalOptions): Promise<boolean> {
let options = undefined;
if (monacoOptions) {
if ('openToSide' in monacoOptions && monacoOptions.openToSide) {
options = Object.assign(options || {}, <WidgetOpenerOptions>{
widgetOptions: {
mode: 'split-right'
}
});
}
if ('openExternal' in monacoOptions && monacoOptions.openExternal) {
options = Object.assign(options || {}, <HttpOpenHandlerOptions>{
openExternal: true
});
}
}
const uri = new URI(monacoUri.toString());
try {
await open(this.openerService, uri, options);
return true;
} catch (e) {
console.error(`Fail to open '${uri.toString()}':`, e);
return false;
}
}

/**
* Suppresses Monaco keydown listener to avoid triggering default Monaco keybindings
* if they are overriden by a user. Monaco keybindings should be registered as Theia keybindings
Expand Down
5 changes: 3 additions & 2 deletions packages/monaco/src/browser/monaco-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export function loadMonaco(vsRequire: any): Promise<void> {
'vs/platform/configuration/common/configurationModels',
'vs/editor/browser/services/codeEditorService',
'vs/editor/browser/services/codeEditorServiceImpl',
'vs/editor/browser/services/openerService',
'vs/platform/markers/common/markerService',
'vs/platform/contextkey/common/contextkey',
'vs/platform/contextkey/browser/contextKeyService',
Expand All @@ -81,7 +82,7 @@ export function loadMonaco(vsRequire: any): Promise<void> {
filters: any, styler: any, colorRegistry: any, color: any,
platform: any, modes: any, suggest: any, snippetParser: any,
configuration: any, configurationModels: any,
codeEditorService: any, codeEditorServiceImpl: any,
codeEditorService: any, codeEditorServiceImpl: any, openerService: any,
markerService: any,
contextKey: any, contextKeyService: any,
error: any) => {
Expand All @@ -90,7 +91,7 @@ export function loadMonaco(vsRequire: any): Promise<void> {
global.monaco.actions = actions;
global.monaco.keybindings = Object.assign({}, keybindingsRegistry, keybindingResolver, resolvedKeybinding, keybindingLabels, keyCodes);
global.monaco.services = Object.assign({}, simpleServices, standaloneServices, configuration, configurationModels,
codeEditorService, codeEditorServiceImpl, markerService);
codeEditorService, codeEditorServiceImpl, markerService, openerService);
global.monaco.quickOpen = Object.assign({}, quickOpenWidget, quickOpenModel);
global.monaco.filters = filters;
global.monaco.theme = styler;
Expand Down
19 changes: 19 additions & 0 deletions packages/monaco/src/typings/monaco/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,25 @@ declare module monaco.keybindings {

declare module monaco.services {

export interface OpenInternalOptions {
readonly openToSide?: boolean;
}

export interface OpenExternalOptions {
readonly openExternal?: boolean
}

export interface IOpener {
open(resource: monaco.Uri | string, options?: OpenInternalOptions | OpenExternalOptions): Promise<boolean>;
}

// https://github.com/TypeFox/vscode/blob/70b8db24a37fafc77247de7f7cb5bb0195120ed0/src/vs/editor/browser/services/openerService.ts#L18
export class OpenerService {
constructor(editorService: monaco.editor.ICodeEditorService, commandService: monaco.commands.ICommandService);
registerOpener(opener: IOpener): monaco.IDisposable;
open(resource: monaco.Uri, options?: { openToSide?: boolean }): Promise<boolean>;
}

export const ICodeEditorService: any;
export const IConfigurationService: any;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ import '../../../src/main/browser/style/index.css';

import { ContainerModule } from 'inversify';
import {
FrontendApplicationContribution, FrontendApplication, WidgetFactory, bindViewContribution,
ViewContainerIdentifier, ViewContainer, createTreeContainer, TreeImpl, TreeWidget, TreeModelImpl, OpenHandler, LabelProviderContribution
FrontendApplicationContribution, WidgetFactory, bindViewContribution,
ViewContainerIdentifier, ViewContainer, createTreeContainer, TreeImpl, TreeWidget, TreeModelImpl, LabelProviderContribution
} from '@theia/core/lib/browser';
import { MaybePromise, CommandContribution, ResourceResolver, bindContributionProvider } from '@theia/core/lib/common';
import { WebSocketConnectionProvider } from '@theia/core/lib/browser/messaging';
Expand Down Expand Up @@ -66,7 +66,6 @@ import { InPluginFileSystemWatcherManager } from './in-plugin-filesystem-watcher
import { WebviewWidget, WebviewWidgetIdentifier, WebviewWidgetExternalEndpoint } from './webview/webview';
import { WebviewEnvironment } from './webview/webview-environment';
import { WebviewThemeDataProvider } from './webview/webview-theme-data-provider';
import { PluginCommandOpenHandler } from './plugin-command-open-handler';
import { bindWebviewPreferences } from './webview/webview-preferences';
import { WebviewResourceLoader, WebviewResourceLoaderPath } from '../common/webview-protocol';
import { WebviewResourceCache } from './webview/webview-resource-cache';
Expand Down Expand Up @@ -106,7 +105,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(ResourceResolver).toService(UntitledResourceResolver);

bind(FrontendApplicationContribution).toDynamicValue(ctx => ({
onStart(app: FrontendApplication): MaybePromise<void> {
onStart(): MaybePromise<void> {
ctx.container.get(HostedPluginSupport).onStart(ctx.container);
}
}));
Expand Down Expand Up @@ -157,9 +156,6 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
}
})).inSingletonScope();

bind(PluginCommandOpenHandler).toSelf().inSingletonScope();
bind(OpenHandler).toService(PluginCommandOpenHandler);

bindWebviewPreferences(bind);
bind(WebviewEnvironment).toSelf().inSingletonScope();
bind(WebviewThemeDataProvider).toSelf().inSingletonScope();
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-ext/src/main/browser/text-editor-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ export class TextEditorMain implements Disposable {
}
}

// TODO move to monaco typings!
interface SnippetController2 extends monaco.editor.IEditorContribution {
insert(template: string,
overwriteBefore: number, overwriteAfter: number,
Expand Down

0 comments on commit 9af5ee3

Please sign in to comment.