From 7471a0c3e0f09391438717b127416c369e8686e5 Mon Sep 17 00:00:00 2001 From: Nicholas Gates Date: Fri, 11 Aug 2017 09:16:10 -0400 Subject: [PATCH] Add support for CodeActions triggering commands on language servers Signed-off-by: Nicholas Gates --- example/src/client.ts | 4 ++-- src/commands.ts | 19 +++++++++++++++++++ src/index.ts | 1 + src/services.ts | 8 +++++--- src/workspace.ts | 38 +++++++++++++++++++++++++++++++++++--- 5 files changed, 62 insertions(+), 8 deletions(-) create mode 100644 src/commands.ts diff --git a/example/src/client.ts b/example/src/client.ts index a65370710..96f5be95f 100644 --- a/example/src/client.ts +++ b/example/src/client.ts @@ -22,7 +22,7 @@ const value = `{ "$schema": "http://json.schemastore.org/coffeelint", "line_endings": "unix" }`; -monaco.editor.create(document.getElementById("container")!, { +const editor = monaco.editor.create(document.getElementById("container")!, { model: monaco.editor.createModel(value, 'json', monaco.Uri.parse('inmemory://model.json')) }); @@ -40,7 +40,7 @@ listen({ } }); -const services = createMonacoServices(); +const services = createMonacoServices(editor); function createLanguageClient(connection: MessageConnection): BaseLanguageClient { return new BaseLanguageClient({ name: "Sample Language Client", diff --git a/src/commands.ts b/src/commands.ts new file mode 100644 index 000000000..5e0045e7f --- /dev/null +++ b/src/commands.ts @@ -0,0 +1,19 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) 2017 TypeFox GmbH (http://www.typefox.io). All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + * ------------------------------------------------------------------------------------------ */ +import { Commands, Disposable } from 'vscode-base-languageclient/lib/services'; + +export class MonacoCommands implements Commands { + + public constructor(private _editor: monaco.editor.IStandaloneCodeEditor) { } + + public registerCommand(command: string, callback: (...args: any[]) => any, thisArg?: any): Disposable { + return (this._editor as any)._commandService.addCommand(command, { + handler: (id: string, ...args: any[]) => { + console.log("Executing command", command, id, args); + callback(...args); + } + }); + } +} diff --git a/src/index.ts b/src/index.ts index f20a6da3e..69ad75983 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. * ------------------------------------------------------------------------------------------ */ export * from './disposable'; +export * from './commands'; export * from './console-window'; export * from './languages'; export * from './workspace'; diff --git a/src/services.ts b/src/services.ts index 6037c0063..1736fcb01 100644 --- a/src/services.ts +++ b/src/services.ts @@ -4,16 +4,18 @@ * ------------------------------------------------------------------------------------------ */ import { BaseLanguageClient } from "vscode-base-languageclient/lib/base"; import { MonacoToProtocolConverter, ProtocolToMonacoConverter } from "./converter"; +import { MonacoCommands } from './commands'; import { MonacoLanguages } from "./languages"; import { MonacoWorkspace } from "./workspace"; import { ConsoleWindow } from "./console-window"; -export function createMonacoServices(): BaseLanguageClient.IServices { +export function createMonacoServices(editor: monaco.editor.IStandaloneCodeEditor): BaseLanguageClient.IServices { const m2p = new MonacoToProtocolConverter(); const p2m = new ProtocolToMonacoConverter(); return { + commands: new MonacoCommands(editor), languages: new MonacoLanguages(p2m, m2p), - workspace: new MonacoWorkspace(m2p), - window: new ConsoleWindow() + workspace: new MonacoWorkspace(p2m, m2p), + window: new ConsoleWindow(), } } \ No newline at end of file diff --git a/src/workspace.ts b/src/workspace.ts index 0f6497060..3c8d8966b 100644 --- a/src/workspace.ts +++ b/src/workspace.ts @@ -2,8 +2,9 @@ * Copyright (c) 2017 TypeFox GmbH (http://www.typefox.io). All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. * ------------------------------------------------------------------------------------------ */ -import { MonacoToProtocolConverter } from './converter'; +import { MonacoToProtocolConverter, ProtocolToMonacoConverter } from './converter'; import { Workspace, TextDocumentDidChangeEvent, TextDocument, Event, Emitter } from "vscode-base-languageclient/lib/services"; +import { WorkspaceEdit, TextEdit } from 'vscode-base-languageclient/lib/base'; import IModel = monaco.editor.IModel; export class MonacoWorkspace implements Workspace { @@ -15,8 +16,7 @@ export class MonacoWorkspace implements Workspace { protected readonly onDidCloseTextDocumentEmitter = new Emitter(); protected readonly onDidChangeTextDocumentEmitter = new Emitter(); - constructor( - protected readonly m2p: MonacoToProtocolConverter) { + constructor(protected readonly p2m: ProtocolToMonacoConverter, protected readonly m2p: MonacoToProtocolConverter) { for (const model of monaco.editor.getModels()) { this.addModel(model); } @@ -83,4 +83,36 @@ export class MonacoWorkspace implements Workspace { return this.onDidChangeTextDocumentEmitter.event; } + public applyEdit(workspaceEdit: WorkspaceEdit): Thenable { + let applied = true; + if (workspaceEdit.documentChanges) { + for (const change of workspaceEdit.documentChanges) { + if (change.textDocument.version && change.textDocument.version >= 0) { + const textDocument = this.documents.get(change.textDocument.uri); + if (textDocument && textDocument.version === change.textDocument.version) { + monaco.editor.getModel(monaco.Uri.parse(textDocument.uri)).pushEditOperations( + [], // Do not try and preserve editor selections. + change.edits.map((edit: TextEdit) => { + return { + identifier: {major: 1, minor: 0}, + range: this.p2m.asRange(edit.range), + text: edit.newText, + forceMoveMarkers: true, + }; + }), + () => [], // Do not try and preserve editor selections. + ); + } else { + applied = false; + } + } else { + applied = false; + } + } + } else { + applied = false; + } + return Promise.resolve(applied); + } + }