Skip to content

Commit

Permalink
Fix #130 Add support for textDocument/onTypeFormatting
Browse files Browse the repository at this point in the history
If an escape character is inserted by the user and only whitespace
follows the character, the subsequent line will be indented.
Formatting will not be performed on the following line if the user is
currently typing inside a comment or a parser directive.

Signed-off-by: Remy Suen <[email protected]>
  • Loading branch information
rcjsuen committed Aug 5, 2017
1 parent 2a066cb commit 34b6d18
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 3 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ All notable changes to this project will be documented in this file.
### Added
- textDocument/codeAction
- created docker.command.convertToLowercase for directives not written in lowercase ([#128](https://github.com/rcjsuen/dockerfile-language-server-nodejs/issues/128))
- textDocument/onTypeFormatting
- format the next line if an escape character is inserted ([#130](https://github.com/rcjsuen/dockerfile-language-server-nodejs/issues/130))
- textDocument/publishDiagnostics
- validate the syntax of LABEL instructions ([#100](https://github.com/rcjsuen/dockerfile-language-server-nodejs/issues/100))
- warn if invalid ONBUILD trigger instructions are used ([#117](https://github.com/rcjsuen/dockerfile-language-server-nodejs/issues/117))
Expand Down
48 changes: 46 additions & 2 deletions src/dockerFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,47 @@ export class DockerFormatter {
}
}

public formatOnType(document: TextDocument, position: Position, ch: string, options: FormattingOptions): TextEdit[] {
const parser = new DockerfileParser();
const dockerfile = parser.parse(document);
// check that the inserted character is the escape character
if (dockerfile.getEscapeCharacter() === ch) {
for (let comment of dockerfile.getComments()) {
// ignore if we're in a comment
if (comment.getRange().start.line === position.line) {
return [];
}
}

const directive = dockerfile.getDirective();
// ignore if we're in the parser directive
if (directive && position.line === 0) {
return [];
}

const content = document.getText();
validityCheck: for (let i = document.offsetAt(position); i < content.length; i++) {
switch (content.charAt(i)) {
case ' ':
case '\t':
break;
case '\r':
case '\n':
break validityCheck;
default:
// not escaping a newline, no need to format the next line
return [];
}
}

const lines = [ position.line + 1 ];
const indentedLines = [];
indentedLines[lines[0]] = true;
return this.formatLines(document, document.getText(), lines, indentedLines, options);
}
return [];
}

public formatRange(document: TextDocument, range: Range, options?: FormattingOptions): TextEdit[] {
let lines = [];
for (let i = range.start.line; i <= range.end.line; i++) {
Expand Down Expand Up @@ -76,8 +117,6 @@ export class DockerFormatter {
let parser = new DockerfileParser();
let dockerfile = parser.parse(document);
let content = document.getText();
let indentation = this.getIndentation(options);
let edits = [];
let indentedLines = [];
for (let i = 0; i < document.lineCount; i++) {
indentedLines[i] = false;
Expand All @@ -89,7 +128,12 @@ export class DockerFormatter {
indentedLines[i] = true;
}
}
return this.formatLines(document, content, lines, indentedLines, options);
}

private formatLines(document: TextDocument, content: string, lines: number[], indentedLines: boolean[], options: FormattingOptions) {
const indentation = this.getIndentation(options);
const edits = [];
lineCheck: for (let line of lines) {
let startOffset = document.offsetAt(Position.create(line, 0));
for (let i = startOffset; i < content.length; i++) {
Expand Down
14 changes: 13 additions & 1 deletion src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
TextDocumentPositionParams, TextDocumentSyncKind, TextDocument, TextEdit, Hover,
CompletionItem, CodeActionParams, Command, ExecuteCommandParams,
DocumentSymbolParams, SymbolInformation,
DocumentFormattingParams, DocumentRangeFormattingParams, DocumentHighlight,
DocumentFormattingParams, DocumentRangeFormattingParams, DocumentOnTypeFormattingParams, DocumentHighlight,
RenameParams, WorkspaceEdit, Location,
DidChangeTextDocumentParams, DidOpenTextDocumentParams, DidCloseTextDocumentParams, TextDocumentContentChangeEvent
} from 'vscode-languageserver';
Expand Down Expand Up @@ -76,6 +76,10 @@ connection.onInitialize((params): InitializeResult => {
},
documentFormattingProvider: true,
documentRangeFormattingProvider: true,
documentOnTypeFormattingProvider: {
firstTriggerCharacter: '\\',
moreTriggerCharacter: [ '`' ]
},
hoverProvider: true,
documentSymbolProvider: true,
documentHighlightProvider: true,
Expand Down Expand Up @@ -249,6 +253,14 @@ connection.onDocumentRangeFormatting((rangeFormattingParams: DocumentRangeFormat
return [];
});

connection.onDocumentOnTypeFormatting((onTypeFormattingParams: DocumentOnTypeFormattingParams): TextEdit[] => {
const document = documents[onTypeFormattingParams.textDocument.uri];
if (document) {
return formatterProvider.formatOnType(document, onTypeFormattingParams.position, onTypeFormattingParams.ch, onTypeFormattingParams.options);
}
return [];
});

connection.onDidOpenTextDocument((didOpenTextDocumentParams: DidOpenTextDocumentParams): void => {
let document = TextDocument.create(didOpenTextDocumentParams.textDocument.uri, didOpenTextDocumentParams.textDocument.languageId, didOpenTextDocumentParams.textDocument.version, didOpenTextDocumentParams.textDocument.text);
documents[didOpenTextDocumentParams.textDocument.uri] = document;
Expand Down
102 changes: 102 additions & 0 deletions test/dockerFormatter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ function formatRange(document: TextDocument, range: Range, options?: FormattingO
return formatter.formatRange(document, range, options);
}

function formatOnType(document: TextDocument, position: Position, ch: string, options?: FormattingOptions): TextEdit[] {
if (!options) {
options = {
insertSpaces: false,
tabSize: 4
};
}
return formatter.formatOnType(document, position, ch, options);
}

describe("Dockerfile formatter", function() {
describe("document", function() {
describe("whitespace", function() {
Expand Down Expand Up @@ -764,4 +774,96 @@ describe("Dockerfile formatter", function() {
});
});
});

describe("on type", function() {
describe("backslash", function() {
it("one line", function() {
let document = createDocument("FROM node AS ");
let edits = formatOnType(document, Position.create(0, 13), '\\');
assert.equal(edits.length, 0);
});

it("two lines", function() {
let document = createDocument("FROM node AS \n");
let edits = formatOnType(document, Position.create(0, 13), '\\');
assert.equal(edits.length, 0);

document = createDocument("FROM node AS \nsetup");
edits = formatOnType(document, Position.create(0, 13), '\\');
assert.equal(edits.length, 1);
assert.equal(edits[0].newText, "\t");
assert.equal(edits[0].range.start.line, 1);
assert.equal(edits[0].range.start.character, 0);
assert.equal(edits[0].range.end.line, 1);
assert.equal(edits[0].range.end.character, 0);
});

it("three lines", function() {
let document = createDocument("FROM node AS \n\n");
let edits = formatOnType(document, Position.create(0, 13), '\\');
assert.equal(edits.length, 0);

document = createDocument("FROM node AS \nsetup\n");
edits = formatOnType(document, Position.create(0, 13), '\\');
assert.equal(edits.length, 1);
assert.equal(edits[0].newText, "\t");
assert.equal(edits[0].range.start.line, 1);
assert.equal(edits[0].range.start.character, 0);
assert.equal(edits[0].range.end.line, 1);
assert.equal(edits[0].range.end.character, 0);

document = createDocument("FROM node \nAS \nsetup\n");
edits = formatOnType(document, Position.create(0, 10), '\\');
assert.equal(edits.length, 1);
assert.equal(edits[0].newText, "\t");
assert.equal(edits[0].range.start.line, 1);
assert.equal(edits[0].range.start.character, 0);
assert.equal(edits[0].range.end.line, 1);
assert.equal(edits[0].range.end.character, 0);
});

it("directive defined as backtick", function() {
let document = createDocument("#escape=`\nFROM node AS \nsetup");
let edits = formatOnType(document, Position.create(1, 13), '\\');
assert.equal(edits.length, 0);
});

it("nested", function() {
let document = createDocument("SHELL [ \"\", \n \"\" ]");
let edits = formatOnType(document, Position.create(0, 9), '\\');
assert.equal(edits.length, 0);
});

it("comment", function() {
let document = createDocument("# comment\nFROM node");
let edits = formatOnType(document, Position.create(0, 9), '\\');
assert.equal(edits.length, 0);
});

it("directive", function() {
let document = createDocument("#escape=\nFROM node");
let edits = formatOnType(document, Position.create(0, 8), '\\');
assert.equal(edits.length, 0);
});
});

describe("backtick", function() {
it("ignored", function() {
let document = createDocument("FROM node AS \nsetup");
let edits = formatOnType(document, Position.create(0, 13), '`');
assert.equal(edits.length, 0);
});

it("directive defined as backtick", function() {
let document = createDocument("#escape=`\nFROM node AS \nsetup");
let edits = formatOnType(document, Position.create(1, 13), '`');
assert.equal(edits.length, 1);
assert.equal(edits[0].newText, "\t");
assert.equal(edits[0].range.start.line, 2);
assert.equal(edits[0].range.start.character, 0);
assert.equal(edits[0].range.end.line, 2);
assert.equal(edits[0].range.end.character, 0);
});
});
});
});

0 comments on commit 34b6d18

Please sign in to comment.