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

Provide support for commands #21

Merged
merged 1 commit into from
Aug 30, 2017
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
7 changes: 4 additions & 3 deletions example/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ const value = `{
"$schema": "http://json.schemastore.org/coffeelint",
"line_endings": "unix"
}`;
monaco.editor.create(document.getElementById("container")!, {
model: monaco.editor.createModel(value, 'json', monaco.Uri.parse('inmemory://model.json'))
const editor = monaco.editor.create(document.getElementById("container")!, {
model: monaco.editor.createModel(value, 'json', monaco.Uri.parse('inmemory://model.json')),
glyphMargin: true
});

// create the web socket
Expand All @@ -40,7 +41,7 @@ listen({
}
});

const services = createMonacoServices();
const services = createMonacoServices(editor);
function createLanguageClient(connection: MessageConnection): BaseLanguageClient {
return new BaseLanguageClient({
name: "Sample Language Client",
Expand Down
2 changes: 1 addition & 1 deletion example/src/json-server-launcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { start } from "./json-server";
export function launch(socket: rpc.IWebSocket) {
const reader = new rpc.WebSocketMessageReader(socket);
const writer = new rpc.WebSocketMessageWriter(socket);
const asExternalProccess = process.argv.findIndex(value => value === '--external');
const asExternalProccess = process.argv.findIndex(value => value === '--external') !== -1;
if (asExternalProccess)  {
// start the language server as an external process
const extJsonServerPath = path.resolve(__dirname, 'ext-json-server.js');
Expand Down
46 changes: 43 additions & 3 deletions example/src/json-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import Uri from 'vscode-uri';
import { MessageReader, MessageWriter } from "vscode-jsonrpc";
import { IConnection, TextDocuments, createConnection } from 'vscode-languageserver';
import {
TextDocument, Diagnostic, CompletionList, CompletionItem, Hover,
TextDocument, Diagnostic, Command, CompletionList, CompletionItem, Hover,
SymbolInformation, DocumentSymbolParams, TextEdit
} from "vscode-languageserver-types";
import { TextDocumentPositionParams, DocumentRangeFormattingParams } from 'vscode-base-languageclient/lib/protocol';
import { TextDocumentPositionParams, DocumentRangeFormattingParams, ExecuteCommandParams, CodeActionParams } from 'vscode-base-languageclient/lib/protocol';
import { getLanguageService, LanguageService, JSONDocument } from "vscode-json-languageservice";

export function start(reader: MessageReader, writer: MessageWriter): JsonServer {
Expand Down Expand Up @@ -55,22 +55,32 @@ export class JsonServer {
return {
capabilities: {
textDocumentSync: this.documents.syncKind,
codeActionProvider: true,
completionProvider: {
resolveProvider: true,
triggerCharacters: ['"', ':']
},
hoverProvider: true,
documentSymbolProvider: true,
documentRangeFormattingProvider: true
documentRangeFormattingProvider: true,
executeCommandProvider: {
commands: ['json.documentUpper']
}
}
}
});
this.connection.onCodeAction(params =>
this.codeAction(params)
);
this.connection.onCompletion(params =>
this.completion(params)
);
this.connection.onCompletionResolve(item =>
this.resolveCompletion(item)
);
this.connection.onExecuteCommand(params =>
this.executeCommand(params)
);
this.connection.onHover(params =>
this.hover(params)
)
Expand All @@ -86,6 +96,18 @@ export class JsonServer {
this.connection.listen();
}

protected codeAction(params: CodeActionParams): Command[] {
return [{
title: "Upper Case Document",
command: "json.documentUpper",
// Send a VersionedTextDocumentIdentifier
arguments: [{
...params.textDocument,
version: this.documents.get(params.textDocument.uri).version
}]
}];
}

protected format(params: DocumentRangeFormattingParams): TextEdit[] {
const document = this.documents.get(params.textDocument.uri);
return this.jsonService.format(document, params.range, params.options)
Expand All @@ -97,6 +119,24 @@ export class JsonServer {
return this.jsonService.findDocumentSymbols(document, jsonDocument);
}

protected executeCommand(params: ExecuteCommandParams): any {
if (params.command === "json.documentUpper" && params.arguments) {
const versionedTextDocumentIdentifier = params.arguments[0];
this.connection.workspace.applyEdit({
documentChanges: [{
textDocument: versionedTextDocumentIdentifier,
edits: [{
range: {
start: {line: 0, character: 0},
end: {line: Number.MAX_SAFE_INTEGER, character: Number.MAX_SAFE_INTEGER}
},
newText: this.documents.get(versionedTextDocumentIdentifier.uri).getText().toUpperCase()
}]
}]
});
}
}

protected hover(params: TextDocumentPositionParams): Thenable<Hover> {
const document = this.documents.get(params.textDocument.uri);
const jsonDocument = this.getJSONDocument(document);
Expand Down
16 changes: 16 additions & 0 deletions src/commands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/* --------------------------------------------------------------------------------------------
* 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(protected readonly editor: monaco.editor.IStandaloneCodeEditor) { }

public registerCommand(command: string, callback: (...args: any[]) => any, thisArg?: any): Disposable {
return this.editor._commandService.addCommand(command, {
handler: (_accessor, ...args: any[]) => callback(...args)
});
}
}
4 changes: 1 addition & 3 deletions src/converter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,9 +337,7 @@ export class ProtocolToMonacoConverter {


asCodeActions(commands: Command[]): monaco.languages.CodeAction[] {
return this.asCommands(commands).map((command, score) => {
return <monaco.languages.CodeAction>{ command, score }
});
return this.asCommands(commands).map((command, score) => ({ command, score }));
}

asCommand(command: Command): monaco.languages.Command {
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
6 changes: 4 additions & 2 deletions src/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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),
workspace: new MonacoWorkspace(p2m, m2p),
window: new ConsoleWindow()
}
}
53 changes: 51 additions & 2 deletions src/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
* 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 { Workspace, TextDocumentDidChangeEvent, TextDocument, Event, Emitter } from "vscode-base-languageclient/lib/services";
import { MonacoToProtocolConverter, ProtocolToMonacoConverter } from './converter';
import { Workspace, TextDocumentDidChangeEvent, TextDocument, Event, Emitter } from 'vscode-base-languageclient/lib/services';
import { WorkspaceEdit } from 'vscode-base-languageclient/lib/base';
import IModel = monaco.editor.IModel;
import IResourceEdit = monaco.languages.IResourceEdit;

export class MonacoWorkspace implements Workspace {

Expand All @@ -14,6 +16,7 @@ export class MonacoWorkspace implements Workspace {
protected readonly onDidChangeTextDocumentEmitter = new Emitter<TextDocumentDidChangeEvent>();

constructor(
protected readonly p2m: ProtocolToMonacoConverter,
protected readonly m2p: MonacoToProtocolConverter,
protected _rootUri: string | null = null) {
for (const model of monaco.editor.getModels()) {
Expand Down Expand Up @@ -82,4 +85,50 @@ export class MonacoWorkspace implements Workspace {
return this.onDidChangeTextDocumentEmitter.event;
}

public applyEdit(workspaceEdit: WorkspaceEdit): Promise<boolean> {
const edit: monaco.languages.WorkspaceEdit = this.p2m.asWorkspaceEdit(workspaceEdit);

// Collect all referenced models
const models: {[uri: string]: monaco.editor.IModel} = edit.edits.reduce(
(acc: {[uri: string]: monaco.editor.IModel}, currentEdit) => {
acc[currentEdit.resource.toString()] = monaco.editor.getModel(currentEdit.resource);
return acc;
}, {}
);

// If any of the models do not exist, refuse to apply the edit.
if (!Object.keys(models).map(uri => models[uri]).every(model => !!model)) {
return Promise.resolve(false);
}

// Group edits by resource so we can batch them when applying
const editsByResource: {[uri: string]: IResourceEdit[]} = edit.edits.reduce(
(acc: {[uri: string]: IResourceEdit[]}, currentEdit) => {
const uri = currentEdit.resource.toString();
if (!(uri in acc)) {
acc[uri] = [];
}
acc[uri].push(currentEdit);
return acc;
}, {}
);

// Apply edits for each resource
Object.keys(editsByResource).forEach(uri => {
models[uri].pushEditOperations(
[], // Do not try and preserve editor selections.
editsByResource[uri].map(resourceEdit => {
return {
identifier: {major: 1, minor: 0},
range: monaco.Range.lift(resourceEdit.range),
text: resourceEdit.newText,
forceMoveMarkers: true,
};
}),
() => [], // Do not try and preserve editor selections.
);
});
return Promise.resolve(true);
}

}
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
],
"files": [
"node_modules/monaco-editor-core/monaco.d.ts",
"typings/monaco/index.d.ts",
"typings/glob-to-regexp/index.d.ts"
]
}
51 changes: 51 additions & 0 deletions typings/monaco/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/// <reference path='../../node_modules/monaco-editor-core/monaco.d.ts'/>

declare module monaco.editor {
export interface IStandaloneCodeEditor {
readonly _commandService: monaco.services.StandaloneCommandService;
}
}

declare module monaco.commands {

export interface ICommandEvent {
commandId: string;
}

export interface ICommandService {
onWillExecuteCommand: monaco.IEvent<ICommandEvent>;
executeCommand<T>(commandId: string, ...args: any[]): monaco.Promise<T>;
executeCommand(commandId: string, ...args: any[]): monaco.Promise<any>;
}

export interface ICommandHandler {
(accessor: monaco.instantiation.ServicesAccessor, ...args: any[]): void;
}

export interface ICommand {
handler: ICommandHandler;
}
}

declare module monaco.instantiation {
export interface ServiceIdentifier<T> {
(...args: any[]): void;
type: T;
}
export interface ServicesAccessor {
get<T>(id: ServiceIdentifier<T>, isOptional?: typeof optional): T;
}
export interface IInstantiationService {
}
export function optional<T>(serviceIdentifier: ServiceIdentifier<T>): (target: Function, key: string, index: number) => void;
}

declare module monaco.services {
export class StandaloneCommandService implements monaco.commands.ICommandService {
constructor(instantiationService: monaco.instantiation.IInstantiationService);
addCommand(id: string, command: monaco.commands.ICommand): IDisposable;
onWillExecuteCommand: monaco.IEvent<monaco.commands.ICommandEvent>;
executeCommand<T>(commandId: string, ...args: any[]): monaco.Promise<T>;
executeCommand(commandId: string, ...args: any[]): monaco.Promise<any>;
}
}