From f71af13762979384286b931f5ce59388a1dcd359 Mon Sep 17 00:00:00 2001 From: Frederik Ring Date: Tue, 7 Nov 2017 01:40:52 +0100 Subject: [PATCH] add playground command (#1270) * add playground command enables the user to upload the current file or selection to the go playground using package goplay - solves #1211 * separate tool execution from command logic, add test * add tests for error case * ensure better separation between tool calling and extension logic * handle no active editor, clear output channel, check for tool availability by stat-ing binary location * move all logic into function body * Add tests back * Fixing linting error --- .travis.yml | 1 + README.md | 2 ++ package.json | 39 ++++++++++++++++++++++- src/goInstallTools.ts | 2 ++ src/goMain.ts | 3 ++ src/goPlayground.ts | 61 ++++++++++++++++++++++++++++++++++++ test/go.test.ts | 72 +++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 src/goPlayground.ts diff --git a/.travis.yml b/.travis.yml index 92cf0023..80e42af7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -52,6 +52,7 @@ install: - go get -u -v github.com/acroca/go-symbols - go get -u -v github.com/alecthomas/gometalinter - go get -u -v github.com/cweill/gotests/... + - go get -u -v github.com/haya14busa/goplay/cmd/goplay - GO15VENDOREXPERIMENT=1 - if [[ "$(go version)" =~ "go version go1.5" ]]; then echo skipping gometalinter; else gometalinter --install; fi diff --git a/README.md b/README.md index a102816c..b1075559 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ This extension adds rich language support for the Go language to VS Code, includ - Show code coverage - Generate method stubs for interfaces (using `impl`) - [_partially implemented_] Debugging (using `delve`) +- Upload to the Go Playground (using `goplay`) ### IDE Features ![IDE](https://i.giphy.com/xTiTndDHV3GeIy6aNa.gif) @@ -105,6 +106,7 @@ In addition to integrated editing features, the extension also provides several * `Go: Add Tags` Adds configured tags to selected struct fields. * `Go: Remove Tags` Removes configured tags from selected struct fields. * `Go: Generate Interface Stubs` Generates method stubs for given interface +* `Go: Run on Go Playground` Upload the current selection or file to the Go Playground You can access all of the above commands from the command pallet (`Cmd+Shift+P` or `Ctrl+Shift+P`). diff --git a/package.json b/package.json index 0769c14d..d8c98a74 100644 --- a/package.json +++ b/package.json @@ -177,6 +177,11 @@ "command": "go.get.package", "title": "Go: Get Package", "description": "Run `go get -v` on the package on the current line." + }, + { + "command": "go.playground", + "title": "Go: Run on Go Playground", + "description": "Upload the current selection or file to the Go Playground" } ], "debuggers": [ @@ -720,6 +725,32 @@ "description": "Tags and options configured here will be used by the Remove Tags command to remove tags to struct fields. If promptForTags is true, then user will be prompted for tags and options. By default, all tags and options will be removed.", "scope": "resource" }, + "go.playground": { + "type": "object", + "properties": { + "openbrowser": { + "type": "boolean", + "default": true, + "description": "Whether to open the created Go Playground in the default browser" + }, + "share": { + "type": "boolean", + "default": true, + "description": "Whether to make the created Go Playground shareable" + }, + "run": { + "type": "boolean", + "default": true, + "description": "Whether to run the created Go Playground after creation" + }, + "description": "The flags configured here will be passed through to command `goplay`" + }, + "default": { + "openbrowser": true, + "share": true, + "run": true + } + }, "go.editorContextMenuCommands": { "type": "object", "properties": { @@ -777,6 +808,11 @@ "type": "boolean", "default": true, "description": "If true, adds command to run test coverage to the editor context menu" + }, + "playground": { + "type": "boolean", + "default": true, + "description": "If true, adds command to upload the current file or selection to the Go Playground" } }, "default": { @@ -790,7 +826,8 @@ "generateTestForFile": false, "generateTestForPackage": false, "addImport": true, - "testCoverage": true + "testCoverage": true, + "playground": true }, "description": "Experimental Feature: Enable/Disable entries from the context menu in the editor.", "scope": "resource" diff --git a/src/goInstallTools.ts b/src/goInstallTools.ts index 709cfa22..f695d5d7 100644 --- a/src/goInstallTools.ts +++ b/src/goInstallTools.ts @@ -26,6 +26,7 @@ const allTools: { [key: string]: string } = { 'guru': 'golang.org/x/tools/cmd/guru', 'gorename': 'golang.org/x/tools/cmd/gorename', 'gomodifytags': 'github.com/fatih/gomodifytags', + 'goplay': 'github.com/haya14busa/goplay/cmd/goplay', 'impl': 'github.com/josharian/impl', 'gotype-live': 'github.com/tylerb/gotype-live', 'godef': 'github.com/rogpeppe/godef', @@ -51,6 +52,7 @@ function getTools(goVersion: SemVersion): string[] { 'guru', 'gorename', 'gomodifytags', + 'goplay', 'impl' ]; diff --git a/src/goMain.ts b/src/goMain.ts index 6d769845..17f4cbe6 100644 --- a/src/goMain.ts +++ b/src/goMain.ts @@ -40,6 +40,7 @@ import { implCursor } from './goImpl'; import { browsePackages } from './goBrowsePackage'; import { goGetPackage } from './goGetPackage'; import { GoDebugConfigurationProvider } from './goDebugConfiguration'; +import { playgroundCommand } from './goPlayground'; export let errorDiagnosticCollection: vscode.DiagnosticCollection; let warningDiagnosticCollection: vscode.DiagnosticCollection; @@ -279,6 +280,8 @@ export function activate(ctx: vscode.ExtensionContext): void { ctx.subscriptions.push(vscode.commands.registerCommand('go.get.package', goGetPackage)); + ctx.subscriptions.push(vscode.commands.registerCommand('go.playground', playgroundCommand)); + vscode.languages.setLanguageConfiguration(GO_MODE.language, { indentationRules: { decreaseIndentPattern: /^\s*(\bcase\b.*:|\bdefault\b:|}[),]?|\)[,]?)$/, diff --git a/src/goPlayground.ts b/src/goPlayground.ts new file mode 100644 index 00000000..5f68fa4e --- /dev/null +++ b/src/goPlayground.ts @@ -0,0 +1,61 @@ +import vscode = require('vscode'); +import * as path from 'path'; +import { execFile } from 'child_process'; +import { outputChannel } from './goStatus'; +import { getBinPath } from './util'; +import { promptForMissingTool } from './goInstallTools'; + +const TOOL_CMD_NAME = 'goplay'; + +type flags = { [key: string]: Boolean }; + +export const playgroundCommand = () => { + const editor = vscode.window.activeTextEditor; + if (!editor) { + vscode.window.showInformationMessage('No editor is active.'); + return; + } + + const binaryLocation = getBinPath(TOOL_CMD_NAME); + if (!path.isAbsolute(binaryLocation)) { + return promptForMissingTool(TOOL_CMD_NAME); + } + + outputChannel.clear(); + outputChannel.show(); + outputChannel.appendLine('Upload to the Go Playground in progress...\n'); + + const selection = editor.selection; + const code = selection.isEmpty + ? editor.document.getText() + : editor.document.getText(selection); + goPlay(code, vscode.workspace.getConfiguration('go', editor.document.uri).get('playground')).then(result => { + outputChannel.append(result); + }, (e: string) => { + if (e) { + vscode.window.showErrorMessage(e); + } + }); +}; + +export function goPlay(code: string, goConfig: vscode.WorkspaceConfiguration): Thenable { + const cliArgs = Object.keys(goConfig).map(key => `-${key}=${goConfig[key]}`); + const binaryLocation = getBinPath(TOOL_CMD_NAME); + + return new Promise((resolve, reject) => { + execFile(binaryLocation, [...cliArgs, '-'], (err, stdout, stderr) => { + if (err && (err).code === 'ENOENT') { + promptForMissingTool(TOOL_CMD_NAME); + return reject(); + } + if (err) { + return reject(`${TOOL_CMD_NAME}: ${stdout || stderr || err.message}`); + } + return resolve( + `Output from the Go Playground: +${stdout || stderr} +Finished running tool: ${binaryLocation} ${cliArgs.join(' ')} -\n` + ); + }).stdin.end(code); + }); +} diff --git a/test/go.test.ts b/test/go.test.ts index f5363819..52ee0e51 100644 --- a/test/go.test.ts +++ b/test/go.test.ts @@ -23,6 +23,7 @@ import { listPackages } from '../src/goImport'; import { generateTestCurrentFile, generateTestCurrentPackage, generateTestCurrentFunction } from '../src/goGenerateTests'; import { getAllPackages } from '../src/goPackages'; import { getImportPath } from '../src/util'; +import { goPlay } from '../src/goPlayground'; suite('Go Extension Tests', () => { let gopath = process.env['GOPATH']; @@ -755,4 +756,75 @@ It returns the number of bytes written and any write error encountered. assert.equal(run[1], getImportPath(run[0])); }); }); + + test('goPlay - success run', (done) => { + const validCode = ` + package main + import ( + "fmt" + ) + func main() { + for i := 1; i < 4; i++ { + fmt.Printf("%v ", i) + } + fmt.Print("Go!") + }`; + const goConfig = Object.create(vscode.workspace.getConfiguration('go'), { + 'playground': { value: { run: true, openbrowser: false, share: false } } + }); + + goPlay(validCode, goConfig['playground']).then(result => { + assert( + result.includes('1 2 3 Go!') + ); + }, (e) => { + assert.ifError(e); + }).then(() => done(), done); + }); + + + test('goPlay - success run & share', (done) => { + const validCode = ` + package main + import ( + "fmt" + ) + func main() { + for i := 1; i < 4; i++ { + fmt.Printf("%v ", i) + } + fmt.Print("Go!") + }`; + const goConfig = Object.create(vscode.workspace.getConfiguration('go'), { + 'playground': { value: { run: true, openbrowser: false, share: true } } + }); + + goPlay(validCode, goConfig['playground']).then(result => { + assert(result.includes('1 2 3 Go!')); + assert(result.includes('https://play.golang.org/')); + }, (e) => { + assert.ifError(e); + }).then(() => done(), done); + }); + + test('goPlay - fail', (done) => { + const invalidCode = ` + package main + import ( + "fmt" + ) + func fantasy() { + fmt.Print("not a main package, sorry") + }`; + const goConfig = Object.create(vscode.workspace.getConfiguration('go'), { + 'playground': { value: { run: true, openbrowser: false, share: false } } + }); + + goPlay(invalidCode, goConfig['playground']).then(result => { + assert.ifError(result); + }, (e) => { + assert.ok(e); + }).then(() => done(), done); + }); + });