Skip to content

Commit

Permalink
Fix #225 Add support for code action literals
Browse files Browse the repository at this point in the history
Instead of returning Commands in textDocument/codeActions, return
CodeActions directly if the client supports this.

Signed-off-by: Remy Suen <remy.suen@gmail.com>
  • Loading branch information
rcjsuen committed Aug 13, 2018
1 parent 6f2cd01 commit 74cc972
Show file tree
Hide file tree
Showing 5 changed files with 222 additions and 53 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
All notable changes to this project will be documented in this file.

## [Unreleased]
### Added
- textDocument/codeActions
- return code action literals if the client supports it ([#225](https://github.com/rcjsuen/dockerfile-language-server-nodejs/issues/225))

### Changed
- [upgraded the dependency of Mocha](https://github.com/mochajs/mocha/issues/2791) from 3.x to 5.x
- versions prior to 4.x of Mocha dependended on Growl 1.9.2 which contained a [security vulnerability](https://github.com/tj/node-growl/issues/60)
Expand Down
58 changes: 28 additions & 30 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"dependencies": {
"dockerfile-language-service": "0.0.5",
"dockerfile-utils": "0.0.8",
"vscode-languageserver": "^4.1.3"
"vscode-languageserver": "^4.4.0"
},
"devDependencies": {
"@types/mocha": "^2.2.33",
Expand Down
85 changes: 66 additions & 19 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import {
DocumentFormattingParams, DocumentRangeFormattingParams, DocumentOnTypeFormattingParams, DocumentHighlight,
RenameParams, WorkspaceEdit, Location,
DidChangeTextDocumentParams, DidOpenTextDocumentParams, DidCloseTextDocumentParams, TextDocumentContentChangeEvent,
DidChangeConfigurationNotification, ConfigurationItem, DocumentLinkParams, DocumentLink, MarkupKind, VersionedTextDocumentIdentifier, TextDocumentEdit
DidChangeConfigurationNotification, ConfigurationItem, DocumentLinkParams, DocumentLink, MarkupKind,
VersionedTextDocumentIdentifier, TextDocumentEdit, CodeAction, CodeActionKind
} from 'vscode-languageserver';
import { ValidatorSettings, ValidationSeverity } from 'dockerfile-utils';
import { CommandIds, DockerfileLanguageServiceFactory } from 'dockerfile-language-service';
Expand Down Expand Up @@ -50,6 +51,8 @@ let configurationSupport: boolean = false;

let documentChangesSupport: boolean = false;

let codeActionQuickFixSupport: boolean = false;

let documents: { [ uri: string ]: TextDocument } = {};

/**
Expand Down Expand Up @@ -93,6 +96,23 @@ function supportsSnippets(capabilities: ClientCapabilities): boolean {
&& capabilities.textDocument.completion.completionItem.snippetSupport;
}

function supportsCodeActionQuickFixes(capabilities: ClientCapabilities): boolean {
let values = capabilities.textDocument
&& capabilities.textDocument.codeAction
&& capabilities.textDocument.codeAction.codeActionLiteralSupport
&& capabilities.textDocument.codeAction.codeActionLiteralSupport.codeActionKind
&& capabilities.textDocument.codeAction.codeActionLiteralSupport.codeActionKind.valueSet;
if (values === null || values === undefined) {
return false;
}
for (let value of values) {
if (value === CodeActionKind.QuickFix) {
return true;
}
}
return false;
}

/**
* Gets the MarkupKind[] that the client supports for the
* documentation field of a CompletionItem.
Expand Down Expand Up @@ -139,6 +159,7 @@ connection.onInitialize((params: InitializeParams): InitializeResult => {
applyEditSupport = params.capabilities.workspace && params.capabilities.workspace.applyEdit === true;
documentChangesSupport = params.capabilities.workspace && params.capabilities.workspace.workspaceEdit && params.capabilities.workspace.workspaceEdit.documentChanges === true;
configurationSupport = params.capabilities.workspace && params.capabilities.workspace.configuration === true;
codeActionQuickFixSupport = supportsCodeActionQuickFixes(params.capabilities);
return {
capabilities: {
textDocumentSync: TextDocumentSyncKind.Incremental,
Expand Down Expand Up @@ -382,34 +403,60 @@ connection.onDocumentHighlight((textDocumentPosition: TextDocumentPositionParams
});
});

connection.onCodeAction((codeActionParams: CodeActionParams): Command[] => {
connection.onCodeAction((codeActionParams: CodeActionParams): Command[] | PromiseLike<CodeAction[]> => {
if (applyEditSupport && codeActionParams.context.diagnostics.length > 0) {
return service.computeCodeActions(codeActionParams.textDocument, codeActionParams.range, codeActionParams.context);
let commands = service.computeCodeActions(codeActionParams.textDocument, codeActionParams.range, codeActionParams.context);
if (codeActionQuickFixSupport) {
return getDocument(codeActionParams.textDocument.uri).then((document) => {
let codeActions = [];
for (let command of commands) {
let codeAction: CodeAction = {
title: command.title,
kind: CodeActionKind.QuickFix
}
let edit = computeWorkspaceEdit(codeActionParams.textDocument.uri, document, command.command, command.arguments);
if (edit) {
codeAction.edit = edit;
}
codeActions.push(codeAction);
}
return codeActions;
});
}
return commands;
}
return [];
});

function computeWorkspaceEdit(uri: string, document: TextDocument, command: string, args: any[]): WorkspaceEdit {
let edits = service.computeCommandEdits(document.getText(), command, args);
if (edits) {
if (documentChangesSupport) {
let identifier = VersionedTextDocumentIdentifier.create(uri, document.version);
return {
documentChanges: [
TextDocumentEdit.create(identifier, edits)
]
};
} else {
return {
changes: {
[ uri ]: edits
}
};
}
}
return null;
}

connection.onExecuteCommand((params: ExecuteCommandParams): void => {
if (applyEditSupport) {
let uri: string = params.arguments[0];
getDocument(uri).then((document) => {
if (document) {
let edits = service.computeCommandEdits(document.getText(), params.command, params.arguments);
if (edits) {
if (documentChangesSupport) {
let identifier = VersionedTextDocumentIdentifier.create(uri, document.version);
connection.workspace.applyEdit({
documentChanges: [
TextDocumentEdit.create(identifier, edits)
]
});
} else {
connection.workspace.applyEdit({
changes: {
[ uri ]: edits
}
});
}
let workspaceEdit = computeWorkspaceEdit(uri, document, params.command, params.arguments);
if (workspaceEdit) {
connection.workspace.applyEdit(workspaceEdit);
}
}
return null;
Expand Down
126 changes: 123 additions & 3 deletions test/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import * as child_process from "child_process";
import * as assert from "assert";

import { TextDocumentSyncKind, MarkupKind, SymbolKind, InsertTextFormat, CompletionItemKind } from 'vscode-languageserver';
import { TextDocumentSyncKind, MarkupKind, SymbolKind, InsertTextFormat, CompletionItemKind, CodeActionKind } from 'vscode-languageserver';
import { CommandIds } from 'dockerfile-language-service';
import { ValidationCode } from 'dockerfile-utils';

Expand Down Expand Up @@ -33,7 +33,7 @@ function sendNotification(method: string, params: any) {
lspProcess.send(message);
}

function initialize(applyEdit: boolean): number {
function initialize(applyEdit: boolean, codeAction?: any): number {
return sendRequest("initialize", {
rootPath: process.cwd(),
processId: process.pid,
Expand All @@ -48,7 +48,8 @@ function initialize(applyEdit: boolean): number {
},
hover: {
contentFormat: [ MarkupKind.PlainText ]
}
},
codeAction
},
workspace: {
applyEdit: applyEdit,
Expand Down Expand Up @@ -506,6 +507,125 @@ describe("Dockerfile LSP Tests", function() {
lspProcess.on("message", listener224);
});

it("issue #225 commands", function (finished) {
this.timeout(5000);
sendNotification("textDocument/didOpen", {
textDocument: {
languageId: "dockerfile",
version: 1,
uri: "uri://dockerfile/225-commands.txt",
text: "from node"
}
});

const codeActionResponseId = sendRequest("textDocument/codeAction", {
textDocument: {
uri: "uri://dockerfile/225-commands.txt"
},
context: {
diagnostics: [
{
code: ValidationCode.CASING_INSTRUCTION,
range: {
start: {
line: 0,
character: 0
},
end: {
line: 0,
character: 4
}
}
}
]
}
});

const codeActionListener = function (json) {
if (json.id === codeActionResponseId) {
assert.ok(Array.isArray(json.result));
assert.equal(json.result.length, 1);
assert.equal(json.result[0].title, "Convert instruction to uppercase");
assert.equal(json.result[0].command, CommandIds.UPPERCASE);
assert.equal(json.result[0].arguments.length, 2);
assert.equal(json.result[0].arguments[0], "uri://dockerfile/225-commands.txt");
assert.equal(json.result[0].arguments[1].start.line, 0);
assert.equal(json.result[0].arguments[1].start.character, 0);
assert.equal(json.result[0].arguments[1].end.line, 0);
assert.equal(json.result[0].arguments[1].end.character, 4);
lspProcess.removeListener("message", codeActionListener);
finished();
}
};
lspProcess.on("message", codeActionListener);
});

it("issue #225 code actions", function (finished) {
this.timeout(5000);
initialize(true, {
codeActionLiteralSupport: {
codeActionKind: {
valueSet: [
CodeActionKind.QuickFix
]
}
}
});
sendNotification("textDocument/didOpen", {
textDocument: {
languageId: "dockerfile",
version: 1,
uri: "uri://dockerfile/225-codeActions.txt",
text: "from node"
}
});

const codeActionResponseId = sendRequest("textDocument/codeAction", {
textDocument: {
uri: "uri://dockerfile/225-codeActions.txt"
},
context: {
diagnostics: [
{
code: ValidationCode.CASING_INSTRUCTION,
range: {
start: {
line: 0,
character: 0
},
end: {
line: 0,
character: 4
}
}
}
]
}
});

const codeActionListener = function (json) {
if (json.id === codeActionResponseId) {
assert.ok(Array.isArray(json.result));
assert.equal(json.result.length, 1);
assert.equal(json.result[0].title, "Convert instruction to uppercase");

assert.equal(json.result[0].edit.documentChanges.length, 1);
assert.equal(json.result[0].edit.documentChanges[0].textDocument.uri, "uri://dockerfile/225-codeActions.txt");
assert.equal(json.result[0].edit.documentChanges[0].textDocument.version, 1);

assert.equal(json.result[0].edit.documentChanges[0].edits.length, 1);
assert.equal(json.result[0].edit.documentChanges[0].edits[0].newText, "FROM");
assert.equal(json.result[0].edit.documentChanges[0].edits[0].range.start.line, 0);
assert.equal(json.result[0].edit.documentChanges[0].edits[0].range.start.character, 0);
assert.equal(json.result[0].edit.documentChanges[0].edits[0].range.end.line, 0);
assert.equal(json.result[0].edit.documentChanges[0].edits[0].range.end.character, 4);
lspProcess.removeListener("message", codeActionListener);
finished();
}
};
lspProcess.on("message", codeActionListener);
});

after(() => {
// terminate the forked LSP process after all the tests have been run
lspProcess.kill();
Expand Down

0 comments on commit 74cc972

Please sign in to comment.