From fa947934cd9b8dd0b3ecd45a9925be66d8a0a0c1 Mon Sep 17 00:00:00 2001 From: Aswin M Prabhu <31558262+aswinmprabhu@users.noreply.github.com> Date: Mon, 4 Mar 2019 02:27:58 +0000 Subject: [PATCH] Add command and code action to extract into function and local variable (#2139) --- src/goDoctor.ts | 103 ++++++++++++++++++++++++++++++++++++++++++ src/goInstallTools.ts | 7 ++- src/goMain.ts | 9 ++++ src/goRefactor.ts | 36 +++++++++++++++ 4 files changed, 153 insertions(+), 2 deletions(-) create mode 100644 src/goDoctor.ts create mode 100644 src/goRefactor.ts diff --git a/src/goDoctor.ts b/src/goDoctor.ts new file mode 100644 index 000000000..a38556330 --- /dev/null +++ b/src/goDoctor.ts @@ -0,0 +1,103 @@ +/*--------------------------------------------------------- + * 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 { getBinPath, getToolsEnvVars } from './util'; +import { promptForMissingTool } from './goInstallTools'; +import { dirname, isAbsolute } from 'path'; + +/** + * Extracts function out of current selection and replaces the current selection with a call to the extracted function. + */ +export function extractFunction() { + extract('extract'); +} + +/** + * Extracts expression out of current selection into a var in the local scope and + * replaces the current selection with the new var. + */ +export function extractVariable() { + extract('var'); +} + +type typeOfExtraction = 'var' | 'extract'; + +async function extract(type: typeOfExtraction): Promise { + let activeEditor = vscode.window.activeTextEditor; + if (!activeEditor) { + vscode.window.showInformationMessage('No editor is active.'); + return; + } + if (activeEditor.selections.length !== 1) { + vscode.window.showInformationMessage( + `You need to have a single selection for extracting ${type === 'var' ? 'variable' : 'method'}` + ); + return; + } + + const newName = await vscode.window.showInputBox({ + placeHolder: 'Please enter a name for the extracted variable.' + }); + + if (!newName) { + return; + } + + runGoDoctor( + newName, + activeEditor.selection, + activeEditor.document.fileName, + type + ); +} + +/** + * @param newName name for the extracted method + * @param selection the editor selection from which method is to be extracted + * @param activeEditor the editor that will be used to apply the changes from godoctor + * @returns errorMessage in case the method fails, null otherwise + */ +function runGoDoctor( + newName: string, + selection: vscode.Selection, + fileName: string, + type: typeOfExtraction +): Thenable { + const godoctor = getBinPath('godoctor'); + + return new Promise((resolve, reject) => { + if (!isAbsolute(godoctor)) { + promptForMissingTool('godoctor'); + return resolve(); + } + + cp.execFile( + godoctor, + [ + '-w', + '-pos', + `${selection.start.line + 1},${selection.start.character + + 1}:${selection.end.line + 1},${selection.end.character}`, + '-file', + fileName, + type, + newName + ], + { + env: getToolsEnvVars(), + cwd: dirname(fileName) + }, + (err, stdout, stderr) => { + if (err) { + vscode.window.showErrorMessage(stderr || err.message); + } + } + ); + }); +} diff --git a/src/goInstallTools.ts b/src/goInstallTools.ts index ea3275d79..c9ef5d449 100644 --- a/src/goInstallTools.ts +++ b/src/goInstallTools.ts @@ -41,6 +41,7 @@ const allToolsWithImportPaths: { [key: string]: string } = { 'go-langserver': 'github.com/sourcegraph/go-langserver', 'dlv': 'github.com/go-delve/delve/cmd/dlv', 'fillstruct': 'github.com/davidrjenni/reftools/cmd/fillstruct', + 'godoctor': 'github.com/godoctor/godoctor', }; function getToolImportPath(tool: string, goVersion: SemVersion) { @@ -132,7 +133,8 @@ function getTools(goVersion: SemVersion): string[] { 'gomodifytags', 'impl', 'fillstruct', - 'goplay' + 'goplay', + 'godoctor' ); return tools; @@ -164,7 +166,8 @@ export function installAllTools(updateExistingToolsOnly: boolean = false) { 'staticcheck': '\t(Linter)', 'go-langserver': '(Language Server)', 'dlv': '\t\t\t(Debugging)', - 'fillstruct': '\t\t(Fill structs with defaults)' + 'fillstruct': '\t\t(Fill structs with defaults)', + 'godoctor': '\t\t(Extract to functions and variables)' }; getGoVersion().then((goVersion) => { diff --git a/src/goMain.ts b/src/goMain.ts index 31229fd3e..9ce976319 100644 --- a/src/goMain.ts +++ b/src/goMain.ts @@ -44,6 +44,7 @@ import { runFillStruct } from './goFillStruct'; import { parseLiveFile } from './goLiveErrors'; import { GoReferencesCodeLensProvider } from './goReferencesCodelens'; import { implCursor } from './goImpl'; +import { extractFunction, extractVariable } from './goDoctor'; import { browsePackages } from './goBrowsePackage'; import { goGetPackage } from './goGetPackage'; import { GoDebugConfigurationProvider } from './goDebugConfiguration'; @@ -55,6 +56,7 @@ import { installCurrentPackage } from './goInstall'; import { setGlobalState } from './stateUtils'; import { ProvideTypeDefinitionSignature } from 'vscode-languageclient/lib/typeDefinition'; import { ProvideImplementationSignature } from 'vscode-languageclient/lib/implementation'; +import { GoRefactorProvider } from './goRefactor'; export let buildDiagnosticCollection: vscode.DiagnosticCollection; export let lintDiagnosticCollection: vscode.DiagnosticCollection; @@ -271,6 +273,7 @@ export function activate(ctx: vscode.ExtensionContext): void { ctx.subscriptions.push(vscode.languages.registerCodeActionsProvider(GO_MODE, new GoCodeActionProvider())); + ctx.subscriptions.push(vscode.languages.registerCodeActionsProvider(GO_MODE, new GoRefactorProvider())); ctx.subscriptions.push(vscode.languages.registerCodeLensProvider(GO_MODE, testCodeLensProvider)); ctx.subscriptions.push(vscode.languages.registerCodeLensProvider(GO_MODE, referencesCodeLensProvider)); ctx.subscriptions.push(vscode.debug.registerDebugConfigurationProvider('go', new GoDebugConfigurationProvider())); @@ -324,6 +327,12 @@ export function activate(ctx: vscode.ExtensionContext): void { ctx.subscriptions.push(vscode.commands.registerCommand('go.impl.cursor', () => { implCursor(); })); + ctx.subscriptions.push(vscode.commands.registerCommand('go.godoctor.extract', () => { + extractFunction(); + })); + ctx.subscriptions.push(vscode.commands.registerCommand('go.godoctor.var', () => { + extractVariable(); + })); ctx.subscriptions.push(vscode.commands.registerCommand('go.test.cursor', (args) => { const goConfig = vscode.workspace.getConfiguration('go', vscode.window.activeTextEditor ? vscode.window.activeTextEditor.document.uri : null); diff --git a/src/goRefactor.ts b/src/goRefactor.ts new file mode 100644 index 000000000..1745b4260 --- /dev/null +++ b/src/goRefactor.ts @@ -0,0 +1,36 @@ +/*--------------------------------------------------------- + * 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'); + +export class GoRefactorProvider implements vscode.CodeActionProvider { + public provideCodeActions( + document: vscode.TextDocument, + range: vscode.Range, + context: vscode.CodeActionContext, + token: vscode.CancellationToken + ): vscode.ProviderResult { + const extractFunction = new vscode.CodeAction( + 'Extract to function in package scope', + vscode.CodeActionKind.RefactorExtract + ); + const extractVar = new vscode.CodeAction( + 'Extract to variable in local scope', + vscode.CodeActionKind.RefactorExtract + ); + extractFunction.command = { + title: 'Extract to function in package scope', + command: 'go.godoctor.extract' + }; + extractVar.command = { + title: 'Extract to variable in local scope', + command: 'go.godoctor.var' + }; + + return [extractFunction, extractVar]; + } +}