Skip to content
This repository has been archived by the owner on Jul 15, 2023. It is now read-only.

Commit

Permalink
Fixes #13 Rename using diffs and parse diffs using diff-parse/diff np…
Browse files Browse the repository at this point in the history
…m modules (#477)

* Rename and Format using diffs if diff tool is available
  • Loading branch information
ramya-rao-a authored Sep 30, 2016
1 parent a443750 commit a0fa2d2
Show file tree
Hide file tree
Showing 10 changed files with 477 additions and 171 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
],
"dependencies": {
"console-stamp": "^0.2.2",
"diff-match-patch": "^1.0.0",
"diff": "~3.0.0",
"json-rpc2": "^1.0.2",
"vscode-debugadapter": "^1.11.0",
"vscode-debugprotocol": "^1.11.0"
Expand Down
172 changes: 172 additions & 0 deletions src/diffUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------*/

import { TextDocument, Position, Range, TextEdit, Uri, WorkspaceEdit, TextEditorEdit } from 'vscode';
import { getBinPathFromEnvVar } from '../src/goPath';
import jsDiff = require('diff');

let diffToolAvailable: boolean = null;

export function isDiffToolAvailable(): boolean {
if (diffToolAvailable == null) {
diffToolAvailable = getBinPathFromEnvVar('diff', 'PATH', false) != null;
}
return diffToolAvailable;
}

export enum EditTypes { EDIT_DELETE, EDIT_INSERT, EDIT_REPLACE};

export class Edit {
action: number;
start: Position;
end: Position;
text: string;

constructor(action: number, start: Position) {
this.action = action;
this.start = start;
this.text = '';
}

// Creates TextEdit for current Edit
apply(): TextEdit {
switch (this.action) {
case EditTypes.EDIT_INSERT:
return TextEdit.insert(this.start, this.text);

case EditTypes.EDIT_DELETE:
return TextEdit.delete(new Range(this.start, this.end));

case EditTypes.EDIT_REPLACE:
return TextEdit.replace(new Range(this.start, this.end), this.text);
}
}

// Applies Edit using given TextEditorEdit
applyUsingTextEditorEdit(editBuilder: TextEditorEdit): void {
switch (this.action) {
case EditTypes.EDIT_INSERT:
editBuilder.insert(this.start, this.text);
break;

case EditTypes.EDIT_DELETE:
editBuilder.delete(new Range(this.start, this.end));
break;

case EditTypes.EDIT_REPLACE:
editBuilder.replace(new Range(this.start, this.end), this.text);
break;
}
}

// Applies Edits to given WorkspaceEdit
applyUsingWorkspaceEdit(workspaceEdit: WorkspaceEdit, fileUri: Uri): void {
switch (this.action) {
case EditTypes.EDIT_INSERT:
workspaceEdit.insert(fileUri, this.start, this.text);
break;

case EditTypes.EDIT_DELETE:
workspaceEdit.delete(fileUri, new Range(this.start, this.end));
break;

case EditTypes.EDIT_REPLACE:
workspaceEdit.replace(fileUri, new Range(this.start, this.end), this.text);
break;
}
}
}

export interface FilePatch {
fileName: string;
edits: Edit[];
}

/**
* Uses diff module to parse given array of IUniDiff objects and returns edits for files
*
* @param diffOutput jsDiff.IUniDiff[]
*
* @returns Array of FilePatch objects, one for each file
*/
function parseUniDiffs(diffOutput: jsDiff.IUniDiff[]): FilePatch[] {
let filePatches: FilePatch[] = [];
diffOutput.forEach((uniDiff: jsDiff.IUniDiff) => {
let edit: Edit = null;
let edits: Edit[] = [];
uniDiff.hunks.forEach((hunk: jsDiff.IHunk) => {
let startLine = hunk.oldStart;
hunk.lines.forEach((line) => {
switch (line.substr(0, 1)) {
case '-':
if (edit == null) {
edit = new Edit(EditTypes.EDIT_DELETE, new Position(startLine - 1, 0));
}
edit.end = new Position(startLine, 0);
startLine++;
break;
case '+':
if (edit == null) {
edit = new Edit(EditTypes.EDIT_INSERT, new Position(startLine - 1, 0));
} else if (edit.action === EditTypes.EDIT_DELETE) {
edit.action = EditTypes.EDIT_REPLACE;
}
edit.text += line.substr(1) + '\n';
break;
case ' ':
startLine++;
if (edit != null) {
edits.push(edit);
}
edit = null;
break;
}
});
if (edit != null) {
edits.push(edit);
}
});
filePatches.push({fileName: uniDiff.oldFileName, edits: edits});
});

return filePatches;

}

/**
* Returns a FilePatch object by generating diffs between given oldStr and newStr using the diff module
*
* @param fileName string: Name of the file to which edits should be applied
* @param oldStr string
* @param newStr string
*
* @returns A single FilePatch object
*/
export function getEdits(fileName: string, oldStr: string, newStr: string): FilePatch {
if (process.platform === 'win32') {
oldStr = oldStr.split('\r\n').join('\n');
newStr = newStr.split('\r\n').join('\n');
}
let unifiedDiffs: jsDiff.IUniDiff = jsDiff.structuredPatch(fileName, fileName, oldStr, newStr, '', '');
let filePatches: FilePatch[] = parseUniDiffs([unifiedDiffs]);
return filePatches[0];
}

/**
* Uses diff module to parse given diff string and returns edits for files
*
* @param diffStr : Diff string in unified format. http://www.gnu.org/software/diffutils/manual/diffutils.html#Unified-Format
*
* @returns Array of FilePatch objects, one for each file
*/
export function getEditsFromUnifiedDiffStr(diffstr: string): FilePatch[] {
// Workaround for the bug https://github.com/kpdecker/jsdiff/issues/135
if (diffstr.startsWith('---')) {
diffstr = diffstr.split('---').join('Index\n---');
}
let unifiedDiffs: jsDiff.IUniDiff[] = jsDiff.parsePatch(diffstr);
let filePatches: FilePatch[] = parseUniDiffs(unifiedDiffs);
return filePatches;
}
98 changes: 12 additions & 86 deletions src/goFormat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,37 +8,10 @@
import vscode = require('vscode');
import cp = require('child_process');
import path = require('path');
import dmp = require('diff-match-patch');
import { isDiffToolAvailable, getEdits, getEditsFromUnifiedDiffStr } from '../src/diffUtils';
import { getBinPath } from './goPath';
import { promptForMissingTool } from './goInstallTools';

let EDIT_DELETE = 0;
let EDIT_INSERT = 1;
let 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';

Expand All @@ -55,6 +28,10 @@ export class Formatter {

let formatCommandBinPath = getBinPath(this.formatCommand);
let formatFlags = vscode.workspace.getConfiguration('go')['formatFlags'] || [];
let canFormatToolUseDiff = isDiffToolAvailable();
if (canFormatToolUseDiff) {
formatFlags.push('-d');
}

cp.execFile(formatCommandBinPath, [...formatFlags, filename], {}, (err, stdout, stderr) => {
try {
Expand All @@ -63,68 +40,17 @@ export class Formatter {
return resolve(null);
}
if (err) return reject('Cannot format due to syntax errors.');
let text = stdout.toString();
let d = new dmp.diff_match_patch();

let diffs = d.diff_main(document.getText(), text);
let line = 0;
let character = 0;
let edits: vscode.TextEdit[] = [];
let edit: Edit = null;
let textEdits: vscode.TextEdit[] = [];
let filePatch = canFormatToolUseDiff ? getEditsFromUnifiedDiffStr(stdout)[0] : getEdits(filename, document.getText(), stdout);

for (let i = 0; i < diffs.length; i++) {
let start = new vscode.Position(line, character);

// Compute the line/character after the diff is applied.
for (let 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());
}
filePatch.edits.forEach((edit) => {
textEdits.push(edit.apply());
});

return resolve(edits);
return resolve(textEdits);
} catch (e) {
reject(e);
reject('Internal issues while getting diff from formatted content');
}
});
});
Expand Down
49 changes: 24 additions & 25 deletions src/goPath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,41 +12,40 @@ import os = require('os');
let binPathCache: { [bin: string]: string; } = {};
let runtimePathCache: string = null;

export function getBinPath(binname: string) {
binname = correctBinname(binname);
if (binPathCache[binname]) return binPathCache[binname];

// First search each GOPATH workspace's bin folder
if (process.env['GOPATH']) {
let workspaces = process.env['GOPATH'].split(path.delimiter);
for (let i = 0; i < workspaces.length; i++) {
let binpath = path.join(workspaces[i], 'bin', binname);
export function getBinPathFromEnvVar(toolName: string, envVar: string, appendBinToPath: boolean): string {
toolName = correctBinname(toolName);
if (process.env[envVar]) {
let paths = process.env[envVar].split(path.delimiter);
for (let i = 0; i < paths.length; i++) {
let binpath = path.join(paths[i], appendBinToPath ? 'bin' : '', toolName);
if (fs.existsSync(binpath)) {
binPathCache[binname] = binpath;
binPathCache[toolName] = binpath;
return binpath;
}
}
}
return null;
}

export function getBinPath(binname: string) {
if (binPathCache[correctBinname(binname)]) return binPathCache[correctBinname(binname)];

// First search each GOPATH workspace's bin folder
let pathFromGoPath = getBinPathFromEnvVar(binname, 'GOPATH', true);
if (pathFromGoPath) {
return pathFromGoPath;
}

// Then search PATH parts
if (process.env['PATH']) {
let pathparts = process.env['PATH'].split(path.delimiter);
for (let i = 0; i < pathparts.length; i++) {
let binpath = path.join(pathparts[i], binname);
if (fs.existsSync(binpath)) {
binPathCache[binname] = binpath;
return binpath;
}
}
let pathFromPath = getBinPathFromEnvVar(binname, 'PATH', false);
if (pathFromPath) {
return pathFromPath;
}

// Finally check GOROOT just in case
if (process.env['GOROOT']) {
let binpath = path.join(process.env['GOROOT'], 'bin', binname);
if (fs.existsSync(binpath)) {
binPathCache[binname] = binpath;
return binpath;
}
let pathFromGoRoot = getBinPathFromEnvVar(binname, 'GOROOT', true);
if (pathFromGoRoot) {
return pathFromGoRoot;
}

// Else return the binary name directly (this will likely always fail downstream)
Expand Down
Loading

0 comments on commit a0fa2d2

Please sign in to comment.