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

Add command to extract method out of selected code #404

Closed
Closed
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
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ install:
- go get -u -v golang.org/x/tools/cmd/guru
- go get -u -v github.com/alecthomas/gometalinter
- gometalinter --install --update
- go get -u -v github.com/godoctor/godoctor

script:
- npm run lint
- npm test --silent

env:
- GO15VENDOREXPERIMENT=1
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ This extension adds rich language support for the Go language to VS Code, includ
- Format (using `goreturns` or `goimports` or `gofmt`)
- Add Imports (using `gopkgs`)
- [_partially implemented_] Debugging (using `delve`)
- Extract Method (using `godoctor`)

### IDE Features
![IDE](http://i.giphy.com/xTiTndDHV3GeIy6aNa.gif)
Expand Down Expand Up @@ -168,6 +169,7 @@ The extension uses the following tools, installed in the current GOPATH. If any
- gopkgs: `go get -u -v github.com/tpng/gopkgs`
- go-symbols: `go get -u -v github.com/newhook/go-symbols`
- guru: `go get -u -v golang.org/x/tools/cmd/guru`
- godoctor: `go get -u -v github.com/godoctor/godoctor`

To install them just paste and run:
```bash
Expand All @@ -180,8 +182,11 @@ go get -u -v golang.org/x/tools/cmd/gorename
go get -u -v github.com/tpng/gopkgs
go get -u -v github.com/newhook/go-symbols
go get -u -v golang.org/x/tools/cmd/guru
go get -u -v github.com/godoctor/godoctor
```

*Note*: If the version of Go you are using is less than 1.6, then `set GO15VENDOREXPERIMENT=1` to support godoctor

And for debugging:

- delve: Follow the instructions at https://github.com/derekparker/delve/blob/master/Documentation/installation/README.md.
Expand Down
13 changes: 13 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,21 @@
"command": "go.import.add",
"title": "Go: Add Import",
"description": "Add an import declaration"
},
{
"command": "go.method.extract",
"title": "Go: Extract Method",
"description": "Extract Method from current selection"
}
],
"menus": {
"editor/context": [
{
"when": "resourceLangId == go",
"command": "go.method.extract"
}
]
},
"debuggers": [
{
"type": "go",
Expand Down
12 changes: 6 additions & 6 deletions src/debugAdapter/goDebug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,17 +228,17 @@ class Delve {
let str = chunk.toString();
if (this.onstderr) { this.onstderr(str); }
if (!serverRunning) {
serverRunning = true;
connectClient(port, host);
}
serverRunning = true;
connectClient(port, host);
}
});
this.debugProcess.stdout.on('data', chunk => {
let str = chunk.toString();
if (this.onstdout) { this.onstdout(str); }
if (!serverRunning) {
serverRunning = true;
connectClient(port, host);
}
serverRunning = true;
connectClient(port, host);
}
});
this.debugProcess.on('close', function(code) {
// TODO: Report `dlv` crash to user.
Expand Down
115 changes: 115 additions & 0 deletions src/goExtractMethod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*---------------------------------------------------------
* 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 { window, Position, Selection, Range, TextEditor } from 'vscode';
import { getBinPath } from './goPath';
import { EditTypes, Edit, GetEditsFromDiffs } from './util';
import cp = require('child_process');
import dmp = require('diff-match-patch');

/**
* Extracts method out of current selection and replaces the current selection with a call to the extracted method.
*/
export function extractMethod() {

let editor = window.activeTextEditor;
if (!editor) {
window.showInformationMessage('No editor is active.');
return;
}
if (editor.selections.length !== 1) {
window.showInformationMessage('You need to have a single selection for extracting method');
return;
}

let showInputBoxPromise = window.showInputBox({placeHolder: 'Please enter the name for the extracted method'});
showInputBoxPromise.then((methodName: string) => {
extractMethodUsingGoDoctor(methodName, editor.selection, editor).then(errorMessage => {
if (errorMessage) {
window.showErrorMessage(errorMessage);
}
});
});
}

/**
* Extracts method out of current selection and replaces the current selection with a call to the extracted method using godoctor.
*
* @param methodName name for the extracted method
* @param selection the editor selection from which method is to be extracted
* @param editor the editor that will be used to apply the changes from godoctor
* @returns errorMessage in case the method fails, null otherwise
*/
export function extractMethodUsingGoDoctor(methodName: string, selection: Selection, editor: TextEditor): Thenable<string> {
let godoctor = getBinPath('godoctor');
let position = `${selection.start.line + 1},${selection.start.character + 1}:${selection.end.line + 1},${selection.end.character + 1}`;

return new Promise((resolve, reject) => {
let process = cp.execFile(godoctor, ['-pos', position, 'extract', methodName], {}, (err, stdout, stderr) => {
if (err) {
let errorMessageIndex = stderr.indexOf('Error:');
return resolve(errorMessageIndex > -1 ? stderr.substr(errorMessageIndex) : stderr);
}

let d = new dmp.diff_match_patch();
let patchText = stdout.substr(stdout.indexOf('@@'));
let patches: dmp.Patch[];

try {
patches = d.patch_fromText(patchText);
}
catch (e) {
return resolve(`Failed to parse the patches from godoctor: ${e.message}`);
}

applypatches(patches, editor).then(validEdit => {
return resolve (validEdit ? null : 'Edits could not be applied to the document');
});

});
process.stdin.end(editor.document.getText());
});
}

/**
* Applies the given set of patches to the document in the given editor
*
* @param patches array of patches to be applied
* @param editor the TextEditor whose document will be updated
*/
function applypatches(patches: dmp.Patch[], editor: TextEditor): Thenable<boolean> {
let totalEdits: Edit[] = [];
patches.reverse().forEach((patch: dmp.Patch) => {
// Godoctor provides a diff for each line, but the text accompanying the diff does not end with '\n'
// GetEditsFromDiffs(..) expects the '\n' to exist in the text wherever there is a new line.
// So add one for each diff from getdoctor
for (let i = 0; i < patch.diffs.length; i++) {
patch.diffs[i][1] += '\n';
}
let edits = GetEditsFromDiffs(patch.diffs, patch.start1);
totalEdits = totalEdits.concat(edits);
});

return editor.edit((editBuilder) => {
totalEdits.forEach((edit) => {
switch (edit.action) {
case EditTypes.EDIT_INSERT:
editBuilder.insert(edit.start, edit.text);
break;
case EditTypes.EDIT_DELETE:
editBuilder.delete(new Range(edit.start, edit.end));
break;
case EditTypes.EDIT_REPLACE:
editBuilder.replace(new Range(edit.start, edit.end), edit.text);
break;
}
});
});
}



86 changes: 8 additions & 78 deletions src/goFormat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,33 +11,7 @@ import path = require('path');
import dmp = require('diff-match-patch');
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);
}
}
}
import { EditTypes, Edit, GetEditsFromDiffs } from './util';

export class Formatter {
private formatCommand = 'goreturns';
Expand Down Expand Up @@ -66,62 +40,18 @@ export class Formatter {
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;

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;
let edits: Edit[] = GetEditsFromDiffs(diffs, 0);
let textEdits: vscode.TextEdit[] = [];

case dmp.DIFF_EQUAL:
if (edit != null) {
edits.push(edit.apply());
edit = null;
}
break;
}
if (!edits) {
return reject('Cannot format due to internal errors');
}

if (edit != null) {
edits.push(edit.apply());
for (let i = 0; i < edits.length; i++) {
textEdits.push(edits[i].apply());
}

return resolve(edits);
return resolve(textEdits);
} catch (e) {
reject(e);
}
Expand Down
6 changes: 5 additions & 1 deletion src/goInstallTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ let tools: { [key: string]: string } = {
'go-outline': 'github.com/lukehoban/go-outline',
'go-symbols': 'github.com/newhook/go-symbols',
'guru': 'golang.org/x/tools/cmd/guru',
'gorename': 'golang.org/x/tools/cmd/gorename'
'gorename': 'golang.org/x/tools/cmd/gorename',
'godoctor': 'github.com/godoctor/godoctor'
};

export function promptForMissingTool(tool: string) {
Expand Down Expand Up @@ -109,6 +110,9 @@ export function setupGoPathAndOfferToInstallTools() {
});

function promptForInstall(missing: string[]) {
// set GO15VENDOREXPERIMENT=1 to support godoctor when using Go v1.5
process.env['GO15VENDOREXPERIMENT'] = 1;

let item = {
title: 'Install',
command() {
Expand Down
5 changes: 5 additions & 0 deletions src/goMain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { showHideStatus } from './goStatus';
import { coverageCurrentPackage, getCodeCoverage, removeCodeCoverage } from './goCover';
import { testAtCursor, testCurrentPackage, testCurrentFile } from './goTest';
import { addImport } from './goImport';
import { extractMethod } from './goExtractMethod';

let diagnosticCollection: vscode.DiagnosticCollection;

Expand Down Expand Up @@ -79,6 +80,10 @@ export function activate(ctx: vscode.ExtensionContext): void {
return addImport(typeof arg === 'string' ? arg : null);
}));

ctx.subscriptions.push(vscode.commands.registerCommand('go.method.extract', () => {
extractMethod();
}));

vscode.languages.setLanguageConfiguration(GO_MODE.language, {
indentationRules: {
// ^(.*\*/)?\s*\}.*$
Expand Down
Loading