├── .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 | 
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 | 
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 | 
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 | }
--------------------------------------------------------------------------------