├── .gitignore ├── typings ├── node.d.ts ├── vscode-typings.d.ts └── tree-kill.d.ts ├── images ├── logo.png ├── open.gif ├── usage.gif └── toggle.png ├── .vscodeignore ├── tsconfig.json ├── .vscode ├── settings.json ├── launch.json └── tasks.json ├── src ├── appInsightsClient.ts └── extension.ts ├── test ├── extension.test.ts └── index.ts ├── vsc-extension-quickstart.md ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules -------------------------------------------------------------------------------- /typings/node.d.ts: -------------------------------------------------------------------------------- 1 | /// -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/formulahendry/vscode-terminal/HEAD/images/logo.png -------------------------------------------------------------------------------- /images/open.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/formulahendry/vscode-terminal/HEAD/images/open.gif -------------------------------------------------------------------------------- /images/usage.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/formulahendry/vscode-terminal/HEAD/images/usage.gif -------------------------------------------------------------------------------- /typings/vscode-typings.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /images/toggle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/formulahendry/vscode-terminal/HEAD/images/toggle.png -------------------------------------------------------------------------------- /typings/tree-kill.d.ts: -------------------------------------------------------------------------------- 1 | declare module "tree-kill" { 2 | export function kill(pid: number, signal?: string, callback?: (err: any) => any); 3 | } -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | typings/** 3 | out/test/** 4 | test/** 5 | src/** 6 | **/*.map 7 | .gitignore 8 | tsconfig.json 9 | vsc-extension-quickstart.md 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es5", 5 | "outDir": "out", 6 | "noLib": true, 7 | "sourceMap": true, 8 | "rootDir": "." 9 | }, 10 | "exclude": [ 11 | "node_modules", 12 | ".vscode-test" 13 | ] 14 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false // set this to true to hide the "out" folder with the compiled JS files 5 | }, 6 | "search.exclude": { 7 | "out": true // set this to false to include "out" folder in search results 8 | }, 9 | "typescript.tsdk": "./node_modules/typescript/lib" // we want to use the TS server from our node_modules folder to control its version 10 | } -------------------------------------------------------------------------------- /src/appInsightsClient.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import * as vscode from 'vscode'; 3 | 4 | const appInsights = require("applicationinsights"); 5 | 6 | export class AppInsightsClient { 7 | private _client; 8 | private _enableAppInsights; 9 | 10 | constructor() { 11 | this._client = appInsights.getClient("ee8f29f9-bc83-42d1-ab28-2762fe50dd31"); 12 | let config = vscode.workspace.getConfiguration('terminal'); 13 | this._enableAppInsights = config.get('enableAppInsights'); 14 | } 15 | 16 | public sendEvent(eventName: string): void { 17 | if (this._enableAppInsights) { 18 | this._client.trackEvent(eventName); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /test/extension.test.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Note: This example test is leveraging the Mocha test framework. 3 | // Please refer to their documentation on https://mochajs.org/ for help. 4 | // 5 | 6 | // The module 'assert' provides assertion methods from node 7 | import * as assert from 'assert'; 8 | 9 | // You can import and use all API from the 'vscode' module 10 | // as well as import your extension to test it 11 | import * as vscode from 'vscode'; 12 | import * as myExtension from '../src/extension'; 13 | 14 | // Defines a Mocha test suite to group tests of similar kind together 15 | suite("Extension Tests", () => { 16 | 17 | // Defines a Mocha unit test 18 | test("Something 1", () => { 19 | assert.equal(-1, [1, 2, 3].indexOf(5)); 20 | assert.equal(-1, [1, 2, 3].indexOf(0)); 21 | }); 22 | }); -------------------------------------------------------------------------------- /test/index.ts: -------------------------------------------------------------------------------- 1 | // 2 | // PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING 3 | // 4 | // This file is providing the test runner to use when running extension tests. 5 | // By default the test runner in use is Mocha based. 6 | // 7 | // You can provide your own test runner if you want to override it by exporting 8 | // a function run(testRoot: string, clb: (error:Error) => void) that the extension 9 | // host can call to run the tests. The test runner is expected to use console.log 10 | // to report the results back to the caller. When the tests are finished, return 11 | // a possible error to the callback or null if none. 12 | 13 | var testRunner = require('vscode/lib/testrunner'); 14 | 15 | // You can directly control Mocha options by uncommenting the following lines 16 | // See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info 17 | testRunner.configure({ 18 | ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.) 19 | useColors: true // colored output from test results 20 | }); 21 | 22 | module.exports = testRunner; -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | { 3 | "version": "0.1.0", 4 | "configurations": [ 5 | { 6 | "name": "Launch Extension", 7 | "type": "extensionHost", 8 | "request": "launch", 9 | "runtimeExecutable": "${execPath}", 10 | "args": ["--extensionDevelopmentPath=${workspaceRoot}" ], 11 | "stopOnEntry": false, 12 | "sourceMaps": true, 13 | "outDir": "${workspaceRoot}/out/src", 14 | "preLaunchTask": "npm" 15 | }, 16 | { 17 | "name": "Launch Tests", 18 | "type": "extensionHost", 19 | "request": "launch", 20 | "runtimeExecutable": "${execPath}", 21 | "args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/out/test" ], 22 | "stopOnEntry": false, 23 | "sourceMaps": true, 24 | "outDir": "${workspaceRoot}/out/test", 25 | "preLaunchTask": "npm" 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // Available variables which can be used inside of strings. 2 | // ${workspaceRoot}: the root folder of the team 3 | // ${file}: the current opened file 4 | // ${fileBasename}: the current opened file's basename 5 | // ${fileDirname}: the current opened file's dirname 6 | // ${fileExtname}: the current opened file's extension 7 | // ${cwd}: the current working directory of the spawned process 8 | 9 | // A task runner that calls a custom npm script that compiles the extension. 10 | { 11 | "version": "0.1.0", 12 | 13 | // we want to run npm 14 | "command": "npm", 15 | 16 | // the command is a shell script 17 | "isShellCommand": true, 18 | 19 | // show the output window only if unrecognized errors occur. 20 | "showOutput": "silent", 21 | 22 | // we run the custom script "compile" as defined in package.json 23 | "args": ["run", "compile", "--loglevel", "silent"], 24 | 25 | // The tsc compiler is started in watching mode 26 | "isWatching": true, 27 | 28 | // use the standard tsc in watch mode problem matcher to find compile problems in the output. 29 | "problemMatcher": "$tsc-watch" 30 | } -------------------------------------------------------------------------------- /vsc-extension-quickstart.md: -------------------------------------------------------------------------------- 1 | # Welcome to your first VS Code Extension 2 | 3 | ## What's in the folder 4 | * This folder contains all of the files necessary for your extension 5 | * `package.json` - this is the manifest file in which you declare your extension and command. 6 | The sample plugin registers a command and defines its title and command name. With this information 7 | VS Code can show the command in the command palette. It doesn’t yet need to load the plugin. 8 | * `src/extension.ts` - this is the main file where you will provide the implementation of your command. 9 | The file exports one function, `activate`, which is called the very first time your extension is 10 | activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`. 11 | We pass the function containing the implementation of the command as the second parameter to 12 | `registerCommand`. 13 | 14 | ## Get up and running straight away 15 | * press `F5` to open a new window with your extension loaded 16 | * run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World` 17 | * set breakpoints in your code inside `src/extension.ts` to debug your extension 18 | * find output from your extension in the debug console 19 | 20 | ## Make changes 21 | * you can relaunch the extension from the debug toolbar after changing code in `src/extension.ts` 22 | * you can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes 23 | 24 | ## Explore the API 25 | * you can open the full set of our API when you open the file `node_modules/vscode/vscode.d.ts` 26 | 27 | ## Run tests 28 | * open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Launch Tests` 29 | * press `F5` to run the tests in a new window with your extension loaded 30 | * see the output of the test result in the debug console 31 | * make changes to `test/extension.test.ts` or create new test files inside the `test` folder 32 | * by convention, the test runner will only consider files matching the name pattern `**.test.ts` 33 | * you can create folders inside the `test` folder to structure your tests any way you want -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "terminal", 3 | "displayName": "Terminal", 4 | "description": "Terminal for Visual Studio Code", 5 | "version": "0.0.10", 6 | "publisher": "formulahendry", 7 | "icon": "images/logo.png", 8 | "engines": { 9 | "vscode": "^1.6.0" 10 | }, 11 | "categories": [ 12 | "Other", 13 | "Languages" 14 | ], 15 | "keywords": [ 16 | "Terminal", 17 | "Shell", 18 | "Bash", 19 | "CMD", 20 | "powershell" 21 | ], 22 | "bugs": { 23 | "url": "https://github.com/formulahendry/vscode-terminal/issues", 24 | "email": "formulahendry@gmail.com" 25 | }, 26 | "homepage": "https://github.com/formulahendry/vscode-terminal/blob/master/README.md", 27 | "repository": { 28 | "type": "git", 29 | "url": "https://github.com/formulahendry/vscode-terminal.git" 30 | }, 31 | "activationEvents": [ 32 | "*", 33 | "onCommand:terminal.run", 34 | "onCommand:terminal.open", 35 | "onCommand:terminal.toggle" 36 | ], 37 | "main": "./out/src/extension", 38 | "contributes": { 39 | "commands": [ 40 | { 41 | "command": "terminal.run", 42 | "title": "Run Terminal Command" 43 | }, 44 | { 45 | "command": "terminal.stop", 46 | "title": "Stop Terminal Command" 47 | }, 48 | { 49 | "command": "terminal.open", 50 | "title": "Open in Integrated Terminal" 51 | }, 52 | { 53 | "command": "terminal.toggle", 54 | "title": "Toggle Integrated Terminal" 55 | } 56 | ], 57 | "keybindings": [ 58 | { 59 | "command": "terminal.run", 60 | "key": "ctrl+alt+r" 61 | }, 62 | { 63 | "command": "terminal.stop", 64 | "key": "ctrl+alt+c" 65 | }, 66 | { 67 | "command": "terminal.open", 68 | "key": "ctrl+alt+o" 69 | }, 70 | { 71 | "command": "terminal.toggle", 72 | "key": "ctrl+alt+t" 73 | } 74 | ], 75 | "menus": { 76 | "editor/context": [ 77 | { 78 | "when": "!inOutput", 79 | "command": "terminal.open", 80 | "group": "navigation@1" 81 | } 82 | ], 83 | "explorer/context": [ 84 | { 85 | "command": "terminal.open", 86 | "group": "navigation@1" 87 | } 88 | ] 89 | }, 90 | "configuration": { 91 | "type": "object", 92 | "title": "Terminal configuration", 93 | "properties": { 94 | "terminal.enableAppInsights": { 95 | "type": "boolean", 96 | "default": true, 97 | "description": "Whether to enable AppInsights to track user telemetry data." 98 | } 99 | } 100 | } 101 | }, 102 | "scripts": { 103 | "vscode:prepublish": "node ./node_modules/vscode/bin/compile", 104 | "compile": "node ./node_modules/vscode/bin/compile -watch -p ./", 105 | "postinstall": "node ./node_modules/vscode/bin/install" 106 | }, 107 | "dependencies": { 108 | "applicationinsights": "^0.17.1", 109 | "tree-kill": "^1.1.0" 110 | }, 111 | "devDependencies": { 112 | "typescript": "^1.8.5", 113 | "vscode": "^0.11.0" 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Terminal 2 | 3 | Run terminal command directly in Text Editor 4 | 5 | ## Notice 6 | From v0.0.4, this extension will have limited updates for bug fix or feature development, because: 7 | 8 | 1. I have another extension: [Code Runner](https://marketplace.visualstudio.com/items?itemName=formulahendry.code-runner) which is superset of the Terminal extension since it not only supports `powershell, bat/cmd, bash/sh` but also supports other script language like `js, php, python, perl, ruby, go, lua, groovy, vbscript` even `F#, C#`. Moreover, this Code Runner extension has more functions (e.g. working directory support, output syntax highlight, run as a file instead of running a set of commands and so on) and will have more supports and updates in the future. 9 | 10 | 2. VS Code already has basic built-in support for the terminal from v1.2 and add 'run selected text' in v1.3. Besides, the ability to run the entire text of current active text editor will come in v1.5. I have already sent the [Pull request](https://github.com/Microsoft/vscode/pull/9480) and it has been merged. 11 | 12 | ## Features 13 | 14 | * Run all the commands in Text Editor 15 | * Run the selected commands in Text Editor 16 | * Stop the running commands 17 | * View output in Output Window 18 | * Open Integrated Terminal at current file's directory 19 | * Quick way to toggle Integrated Terminal 20 | 21 | ## Usages 22 | 23 | * Write or select a set of commands in Text Editor, then use shortcut `Ctrl+Alt+R`, or press `F1` and then select/type `Run Terminal Command`, the commands will run and the output will be shown in the Output Window. 24 | * To stop the running commands, use shortcut `Ctrl+Alt+C`, or press `F1` and then select/type `Stop Terminal Command` 25 | 26 | ![Usage](images/usage.gif) 27 | 28 | * To open Integrated Terminal at current file's directory, use shortcut `Ctrl+Alt+O`, or press `F1` and then select/type `Open in Integrated Terminal`, or right click in Text Editor/Explorer and then click `Open in Integrated Terminal` in context menu 29 | 30 | ![Open](images/open.gif) 31 | 32 | * To toggle Integrated Terminal, use shortcut `Ctrl+Alt+T`, or click the `Terminal` icon in the Status Bar at the bottom 33 | 34 | ![Toggle](images/toggle.png) 35 | 36 | ## Telemetry data 37 | By default, telemetry data collection is turned on. To disable it, update the settings.json as below: 38 | ```json 39 | { 40 | "terminal.enableAppInsights": false 41 | } 42 | ``` 43 | 44 | ## Change Log 45 | ### 0.0.10 (2017-07-22) 46 | * [#10](https://github.com/formulahendry/vscode-terminal/issues/10): Handle case-insensitive bash path 47 | 48 | ### 0.0.9 (2017-07-20) 49 | * [#9](https://github.com/formulahendry/vscode-terminal/issues/9): Open terminal for Bash on Windows 50 | 51 | ### 0.0.8 (2017-05-15) 52 | * Quick way to toggle Integrated Terminal in the Status Bar 53 | 54 | ### 0.0.7 55 | * Add 'Open in Integrated Terminal' context menu 56 | 57 | ### 0.0.6 58 | * Upgrade applicationinsights npm since [telemetry data requires HTTPS](https://azure.microsoft.com/en-us/updates/application-insights-telemetry-data-now-requires-https-with-shutdown-of-http-data-collectors/) 59 | 60 | ### 0.0.5 61 | * Add Application Insights to track telemetry data 62 | 63 | ### 0.0.4 64 | * Update the future of this extension 65 | 66 | ### 0.0.3 67 | * Add support to stop running commands 68 | 69 | ### 0.0.2 70 | * Update README.md and add GitHub info 71 | 72 | ### 0.0.1 73 | * Initial Release 74 | 75 | ## Issues 76 | Submit the [issues](https://github.com/formulahendry/vscode-terminal/issues) if you find any bug or have any suggestion. 77 | 78 | ## Contribution 79 | Fork the [repo](https://github.com/formulahendry/vscode-terminal) and submit pull requests. -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | // The module 'vscode' contains the VS Code extensibility API 3 | // Import the module and reference it with the alias vscode in your code below 4 | import * as vscode from 'vscode'; 5 | import * as os from 'os'; 6 | import * as path from 'path'; 7 | import { AppInsightsClient } from './appInsightsClient'; 8 | 9 | // this method is called when your extension is activated 10 | // your extension is activated the very first time the command is executed 11 | export function activate(context: vscode.ExtensionContext) { 12 | 13 | // Use the console to output diagnostic information (console.log) and errors (console.error) 14 | // This line of code will only be executed once when your extension is activated 15 | console.log('Congratulations, your extension "terminal" is now active!'); 16 | 17 | let terminal = new Terminal(); 18 | 19 | let run = vscode.commands.registerCommand('terminal.run', () => { 20 | terminal.run(); 21 | }); 22 | 23 | let stop = vscode.commands.registerCommand('terminal.stop', () => { 24 | terminal.stop(); 25 | }); 26 | 27 | let open = vscode.commands.registerCommand('terminal.open', (fileUri) => { 28 | terminal.open(fileUri); 29 | }); 30 | 31 | let toggle = vscode.commands.registerCommand('terminal.toggle', () => { 32 | terminal.toggle(); 33 | }); 34 | 35 | context.subscriptions.push(run); 36 | context.subscriptions.push(stop); 37 | context.subscriptions.push(open); 38 | context.subscriptions.push(toggle); 39 | } 40 | 41 | // this method is called when your extension is deactivated 42 | export function deactivate() { 43 | } 44 | 45 | /** 46 | * Terminal 47 | */ 48 | class Terminal { 49 | private _outputChannel: vscode.OutputChannel; 50 | private _isRunning: boolean; 51 | private _process; 52 | private _appInsightsClient: AppInsightsClient; 53 | 54 | constructor() { 55 | this._outputChannel = vscode.window.createOutputChannel('Terminal'); 56 | this._outputChannel.appendLine('[Notice] This extension will have limited updates in the future, try Code Runner: https://marketplace.visualstudio.com/items?itemName=formulahendry.code-runner with more functions and supports!'); 57 | this._outputChannel.appendLine(''); 58 | this.createStatusBarItem(); 59 | this._appInsightsClient = new AppInsightsClient(); 60 | } 61 | 62 | public run(): void { 63 | this._appInsightsClient.sendEvent("run"); 64 | if (this._isRunning) { 65 | vscode.window.showInformationMessage('Command(s) are already running!'); 66 | return; 67 | } 68 | 69 | let commands = this.getCommands(); 70 | if (commands.length == 0) { 71 | vscode.window.showInformationMessage('No commands found or selected.'); 72 | return; 73 | } 74 | 75 | this._isRunning = true; 76 | this.ExecuteCommands(commands); 77 | } 78 | 79 | public stop(): void { 80 | this._appInsightsClient.sendEvent("stop"); 81 | if (this._isRunning) { 82 | this._isRunning = false; 83 | let kill = require('tree-kill'); 84 | kill(this._process.pid); 85 | this._outputChannel.appendLine(''); 86 | this._outputChannel.appendLine('Command(s) stopped.'); 87 | } 88 | } 89 | 90 | public open(fileUri?: vscode.Uri): void { 91 | let filePath: string; 92 | if (!fileUri || typeof fileUri.fsPath !== 'string') { 93 | let activeEditor = vscode.window.activeTextEditor; 94 | if (activeEditor && !activeEditor.document.isUntitled) { 95 | filePath = activeEditor.document.fileName; 96 | } 97 | } else { 98 | filePath = fileUri.fsPath; 99 | } 100 | 101 | this._appInsightsClient.sendEvent("open"); 102 | let terminal = vscode.window.createTerminal(); 103 | terminal.show(false); 104 | filePath = this.getFilePathForBashOnWindows(filePath); 105 | if (filePath) { 106 | terminal.sendText(`cd "${path.dirname(filePath)}"`); 107 | } 108 | } 109 | 110 | public toggle(): void { 111 | vscode.commands.executeCommand("workbench.action.terminal.toggleTerminal"); 112 | this._appInsightsClient.sendEvent("toggle"); 113 | } 114 | 115 | private getCommands(): string[] { 116 | let editor = vscode.window.activeTextEditor; 117 | if (!editor) { 118 | return []; 119 | } 120 | 121 | let selection = editor.selection; 122 | let text = selection.isEmpty ? editor.document.getText() : editor.document.getText(selection); 123 | let commands = text.trim().split(/\s*[\r\n]+\s*/g).filter(this.filterEmptyString); 124 | 125 | return commands; 126 | } 127 | 128 | private filterEmptyString(value: string): boolean { 129 | return value.length > 0; 130 | } 131 | 132 | private ExecuteCommands(commands: string[]) { 133 | this._outputChannel.show(true); 134 | this.ExecuteCommand(commands, 0); 135 | } 136 | 137 | private ExecuteCommand(commands: string[], index: number) { 138 | if (index >= commands.length) { 139 | this._isRunning = false; 140 | return; 141 | } 142 | if (this._isRunning) { 143 | let exec = require('child_process').exec; 144 | this._outputChannel.appendLine('>> ' + commands[index]); 145 | this._process = exec(commands[index]); 146 | 147 | this._process.stdout.on('data', (data) => { 148 | this._outputChannel.append(data); 149 | }); 150 | 151 | this._process.stderr.on('data', (data) => { 152 | this._outputChannel.append(data); 153 | }); 154 | 155 | this._process.on('close', (code) => { 156 | this._outputChannel.appendLine(''); 157 | this.ExecuteCommand(commands, index + 1); 158 | }); 159 | } 160 | } 161 | 162 | private createStatusBarItem(): void { 163 | let toggleTerminalStatusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100); 164 | toggleTerminalStatusBarItem.command = "terminal.toggle"; 165 | toggleTerminalStatusBarItem.text = " $(terminal) "; 166 | toggleTerminalStatusBarItem.tooltip = "Toggle Integrated Terminal"; 167 | toggleTerminalStatusBarItem.show(); 168 | } 169 | 170 | private getFilePathForBashOnWindows(filePath: string): string { 171 | if (os.platform() === 'win32') { 172 | let windowsShell = vscode.workspace.getConfiguration('terminal').get('integrated.shell.windows'); 173 | if (windowsShell && windowsShell.toLowerCase().indexOf('bash') > -1 && windowsShell.toLowerCase().indexOf('windows') > -1) { 174 | filePath = filePath.replace(/([A-Za-z]):\\/, this.replacer).replace(/\\/g, '/'); 175 | } 176 | } 177 | return filePath; 178 | } 179 | 180 | private replacer(match: string, p1: string): string { 181 | return `/mnt/${p1.toLowerCase()}/`; 182 | } 183 | } --------------------------------------------------------------------------------