blob: 9866ab24774e7430a65cf65fec64c96e7b045957 [file] [log] [blame]
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------*/
'use strict';
import vscode = require('vscode');
import cp = require('child_process');
import path = require('path');
import dmp = require('diff-match-patch');
import { getBinPath } from './goPath'
var EDIT_DELETE = 0;
var EDIT_INSERT = 1;
var EDIT_REPLACE = 2;
class Edit {
action: number;
start: vscode.Position;
end: vscode.Position;
text: string;
constructor(action: number, start: vscode.Position) {
this.action = action;
this.start = start;
this.text = "";
}
apply(): vscode.TextEdit {
switch (this.action) {
case EDIT_INSERT:
return vscode.TextEdit.insert(this.start, this.text);
case EDIT_DELETE:
return vscode.TextEdit.delete(new vscode.Range(this.start, this.end));
case EDIT_REPLACE:
return vscode.TextEdit.replace(new vscode.Range(this.start, this.end), this.text);
}
}
}
export class Formatter {
private formatCommand = "goreturns";
constructor() {
let formatTool = vscode.workspace.getConfiguration('go')['formatTool'];
if (formatTool) {
this.formatCommand = formatTool;
}
}
public formatDocument(document: vscode.TextDocument): Thenable<vscode.TextEdit[]> {
return new Promise((resolve, reject) => {
var filename = document.fileName;
var formatCommandBinPath = getBinPath(this.formatCommand);
cp.execFile(formatCommandBinPath, [filename], {}, (err, stdout, stderr) => {
try {
if (err && (<any>err).code == "ENOENT") {
vscode.window.showInformationMessage("The '" + formatCommandBinPath + "' command is not available. Please check your go.formatTool user setting and ensure it is installed.");
return resolve(null);
}
if (err) return reject("Cannot format due to syntax errors.");
var text = stdout.toString();
var d = new dmp.diff_match_patch();
var diffs = d.diff_main(document.getText(), text);
var line = 0;
var character = 0;
var edits: vscode.TextEdit[] = [];
var edit: Edit = null;
for (var i = 0; i < diffs.length; i++) {
var start = new vscode.Position(line, character);
// Compute the line/character after the diff is applied.
for (var curr = 0; curr < diffs[i][1].length; curr++) {
if (diffs[i][1][curr] != '\n') {
character++;
} else {
character = 0;
line++;
}
}
switch (diffs[i][0]) {
case dmp.DIFF_DELETE:
if (edit == null) {
edit = new Edit(EDIT_DELETE, start);
} else if (edit.action != EDIT_DELETE) {
return reject("cannot format due to an internal error.");
}
edit.end = new vscode.Position(line, character);
break;
case dmp.DIFF_INSERT:
if (edit == null) {
edit = new Edit(EDIT_INSERT, start);
} else if (edit.action == EDIT_DELETE) {
edit.action = EDIT_REPLACE;
}
// insert and replace edits are all relative to the original state
// of the document, so inserts should reset the current line/character
// position to the start.
line = start.line;
character = start.character;
edit.text += diffs[i][1];
break;
case dmp.DIFF_EQUAL:
if (edit != null) {
edits.push(edit.apply());
edit = null;
}
break;
}
}
if (edit != null) {
edits.push(edit.apply());
}
return resolve(edits);
} catch (e) {
reject(e);
}
});
});
}
}
export class GoDocumentFormattingEditProvider implements vscode.DocumentFormattingEditProvider {
private formatter: Formatter;
constructor() {
this.formatter = new Formatter();
}
public provideDocumentFormattingEdits(document: vscode.TextDocument, options: vscode.FormattingOptions, token: vscode.CancellationToken): Thenable<vscode.TextEdit[]> {
return document.save().then(() => {
return this.formatter.formatDocument(document);
});
}
}