├── src ├── test │ ├── .python-version │ ├── eg.sh │ ├── sleep.py │ └── eg.py └── extension.ts ├── .gitignore ├── images ├── run-in-terminal.gif └── run-in-terminal.png ├── .vscodeignore ├── .eslintrc.json ├── tsconfig.json ├── .vscode ├── tasks.json ├── settings.json └── launch.json ├── CHANGELOG.md ├── webpack.config.js ├── vsc-extension-quickstart.md ├── package.json └── README.md /src/test/.python-version: -------------------------------------------------------------------------------- 1 | 3.10.0 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | dist 3 | node_modules 4 | .vscode-test/ 5 | *.vsix 6 | -------------------------------------------------------------------------------- /src/test/eg.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | echo "You just ran:" 3 | echo "eg.sh $@" -------------------------------------------------------------------------------- /images/run-in-terminal.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kortina/run-in-terminal/HEAD/images/run-in-terminal.gif -------------------------------------------------------------------------------- /images/run-in-terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kortina/run-in-terminal/HEAD/images/run-in-terminal.png -------------------------------------------------------------------------------- /src/test/sleep.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | import time 4 | 5 | time.sleep(3.0) 6 | sys.exit(0) 7 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/test/** 4 | out/**/*.map 5 | src/** 6 | .gitignore 7 | tsconfig.json 8 | vsc-extension-quickstart.md 9 | -------------------------------------------------------------------------------- /src/test/eg.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class Eg(unittest.TestCase): 5 | """Example python tests for run-in-terminal""" 6 | 7 | def test_one(self): 8 | assert "line 8" == "line 8" 9 | 10 | def test_two(self): 11 | assert "line 11" == "line 11" 12 | 13 | 14 | if __name__ == "__main__": 15 | unittest.main() 16 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "parser": "typescript-eslint-parser", 4 | "parserOptions": { 5 | "ecmaVersion": 6, 6 | "sourceType": "module", 7 | "ecmaFeatures": { 8 | "modules": true 9 | } 10 | }, 11 | "plugins": [ 12 | "typescript" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "lib": [ 7 | "es6" 8 | ], 9 | "sourceMap": true, 10 | "rootDir": "src" 11 | }, 12 | "exclude": [ 13 | "node_modules", 14 | ".vscode-test" 15 | ] 16 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "eslint.options": { 4 | "configFile": ".eslintrc.json" 5 | }, 6 | "files.exclude": { 7 | "out": false // set this to true to hide the "out" folder with the compiled JS files 8 | }, 9 | "search.exclude": { 10 | "out": true // set this to false to include "out" folder in search results 11 | }, 12 | "cSpell.words": [ 13 | "benmills", 14 | "Extname", 15 | "Lopol", 16 | "mrcasals", 17 | "rspec", 18 | "vimux", 19 | "vpackage", 20 | "vpublish", 21 | "vsix", 22 | "xvfb" 23 | ] 24 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "runtimeExecutable": "${execPath}", 13 | "args": ["--extensionDevelopmentPath=${workspaceFolder}", "${workspaceFolder}/src/test/"], 14 | "outFiles": ["${workspaceFolder}/out/**/*.js"], 15 | "preLaunchTask": "npm: watch" 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Release Notes 2 | 3 | ### 0.0.7 4 | 5 | - Fix #34 🤞 by removing call to `Term.term` and using `Term.term.processId` instead of `Term.term.termName`. Tx @Lopol2010 for fix ideas in #41 6 | - Dependabot update #43 and #44 7 | 8 | ### 0.0.6 9 | 10 | - Add new configuration that allows you to choose whether or not to save command in history `runInTerminal.saveCommandInHistory`. Tx @mrcasals for #37 11 | - Merge in dependabot updates. 12 | 13 | ### 0.0.4 14 | 15 | - Fix bug where any project level settings defining runInTerminal.commands were blowing away global and default commands 16 | 17 | ### 0.0.3 18 | 19 | - Undo a bunch of GitHub auto-fixes that broke the extension. 20 | 21 | ### 0.0.2 22 | 23 | - Add webpack config. Add example `sh` and `py` script to test with. Fix typos in README. Fix configuration property title for `runInTerminal.commands`. 24 | 25 | ### 0.0.1 26 | 27 | - Initial release. 28 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | 3 | 'use strict'; 4 | 5 | const path = require('path'); 6 | 7 | /**@type {import('webpack').Configuration}*/ 8 | const config = { 9 | target: 'node', // vscode extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/ 10 | 11 | entry: './src/extension.ts', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/ 12 | output: { 13 | // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/ 14 | path: path.resolve(__dirname, 'dist'), 15 | filename: 'extension.js', 16 | libraryTarget: 'commonjs2', 17 | devtoolModuleFilenameTemplate: '../[resource-path]' 18 | }, 19 | devtool: 'source-map', 20 | externals: { 21 | vscode: 'commonjs vscode' // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/ 22 | }, 23 | resolve: { 24 | // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader 25 | extensions: ['.ts', '.js'] 26 | }, 27 | module: { 28 | rules: [ 29 | { 30 | test: /\.ts$/, 31 | exclude: /node_modules/, 32 | use: [ 33 | { 34 | loader: 'ts-loader' 35 | } 36 | ] 37 | } 38 | ] 39 | } 40 | }; 41 | module.exports = config; -------------------------------------------------------------------------------- /vsc-extension-quickstart.md: -------------------------------------------------------------------------------- 1 | # Welcome to your 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. 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "run-in-terminal", 3 | "displayName": "Run in Terminal", 4 | "description": "Use a keyboard shortcut to run any command in the Integrated Terminal.", 5 | "version": "0.0.7", 6 | "publisher": "kortina", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/kortina/run-in-terminal" 10 | }, 11 | "icon": "images/run-in-terminal.png", 12 | "engines": { 13 | "vscode": "^1.41.1" 14 | }, 15 | "categories": [ 16 | "Other" 17 | ], 18 | "activationEvents": [ 19 | "onCommand:runInTerminal.run", 20 | "onCommand:runInTerminal.runLast" 21 | ], 22 | "main": "./out/extension", 23 | "contributes": { 24 | "commands": [ 25 | { 26 | "command": "runInTerminal.run", 27 | "title": "Run in Terminal" 28 | }, 29 | { 30 | "command": "runInTerminal.runLast", 31 | "title": "Re-Run Last Command in Terminal" 32 | } 33 | ], 34 | "configuration": { 35 | "title": "Run in Terminal Configuration", 36 | "properties": { 37 | "runInTerminal.clearBeforeRun": { 38 | "type": "boolean", 39 | "default": false, 40 | "description": "Clear terminal before run?" 41 | }, 42 | "runInTerminal.saveCommandInHistory": { 43 | "type": "boolean", 44 | "default": false, 45 | "description": "Save command in history?" 46 | }, 47 | "runInTerminal.commands": { 48 | "type": "array", 49 | "items": { 50 | "type": "object", 51 | "properties": { 52 | "name": { 53 | "type": "string", 54 | "description": "`name` of command. When `runInTerminal.run` is called with {'name': 'x'}, the command with `name` = 'x' and `match` =~ the current filename will be executed.", 55 | "default": null 56 | }, 57 | "match": { 58 | "type": "string", 59 | "description": "`match` for filename. When `runInTerminal.run` is called with {'name': 'x'}, the command with `name` = 'x' and `match` =~ the current filename will be executed.", 60 | "default": null 61 | }, 62 | "cmd": { 63 | "type": "string", 64 | "description": "Command to execute if found by `name` and `match`.", 65 | "default": null 66 | } 67 | } 68 | } 69 | } 70 | } 71 | } 72 | }, 73 | "scripts": { 74 | "compile": "tsc -p ./", 75 | "lint": "eslint -c .eslintrc.js --ext .ts src", 76 | "vpackage": "./node_modules/.bin/vsce package", 77 | "vpublish": "./node_modules/.bin/vsce publish", 78 | "vscode:prepublish": "npm run compile", 79 | "watch": "tsc -watch -p ./" 80 | }, 81 | "devDependencies": { 82 | "@types/mocha": "^8.2.0", 83 | "@types/node": "^11.9.0", 84 | "@types/vscode": "^1.32.0", 85 | "@typescript-eslint/eslint-plugin": "^2.28.0", 86 | "@typescript-eslint/parser": "^2.28.0", 87 | "eslint": "^6.8.0", 88 | "typescript": "^3.8.3", 89 | "@vscode/vsce": "^2.19.0", 90 | "@vscode/test-electron": "^2.3.3" 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Run in Terminal, an Extension for `vscode` 2 | 3 | Use a keyboard shortcut to run any command in the Integrated Terminal of [Visual Studio Code](https://code.visualstudio.com/). 4 | 5 | You can [install it from the VSCode Marketplace](https://marketplace.visualstudio.com/items?itemName=kortina.run-in-terminal). 6 | 7 | I built this because I wanted something for `vscode` like benmills' amazing [vimux](https://github.com/benmills/vimux) for `vim`. 8 | 9 | The [send-to-terminal](https://github.com/malkomalko/send-to-terminal) extension was close to what I wanted, 10 | but I wanted to be able to run more than 2 commands per filetype match. 11 | `run-in-terminal` is based on `send-to-terminal`, but allows an arbitrary number of commands per filetype. 12 | 13 | The native `Tasks` functionality in `vscode` is also close, but configuration is only possible at 14 | the project level, [not the global level](https://github.com/Microsoft/vscode/issues/1435). 15 | 16 | Here is what `Run in Terminal` looks like: 17 | 18 | ![demo](images/run-in-terminal.gif) 19 | 20 | ## `keybindings.json` 21 | 22 | The simplest way to configure a `keybindings.json` using the `cmd` argument for the `runInTerminal.run` command: 23 | 24 | ``` 25 | ... 26 | { 27 | "key": "ctrl+e", 28 | "command": "runInTerminal.run", 29 | "args": {"cmd": "/usr/bin/env bash ${relativeFile}", "match": ".*"}, 30 | "when": "resourceLangId == shellscript" 31 | }, 32 | { 33 | "key": "ctrl+e", 34 | "command": "runInTerminal.run", 35 | "args": {"cmd": "/usr/bin/env python ${relativeFile}", "match": ".*"}, 36 | "when": "resourceLangId == python" 37 | }, 38 | ... 39 | ``` 40 | 41 | Note above that when using `keybindings.json` you might use the 'when' context (rather than the `match` expression) to specify different commands with the same keybinding for different filetypes. If you do so, use '.\*' as your match expression. 42 | 43 | ## `settings.json` 44 | 45 | If you are using [VSCodeVim](https://github.com/VSCodeVim/Vim), things look a little bit different, because there is no support for a 'when' context to perform different commands for different filetypes. 46 | 47 | ### 'runInTerminal.commands' 48 | 49 | Here is what you might put in your `settings.json` when configuring with VSCodeVim: 50 | 51 | ``` 52 | ... 53 | "vim.normalModeKeyBindingsNonRecursive": [ 54 | { 55 | "before": ["", "r", "a"], "after": [], 56 | "commands": [ {"command": "runInTerminal.runLast" } ] 57 | }, 58 | { 59 | "before": ["", "r", "l"], "after": [], 60 | "commands": [ {"command": "runInTerminal.run", "args": {"name": "l"}} ] 61 | }, 62 | { 63 | "before": ["", "r", "b"], "after": [], 64 | "commands": [ {"command": "runInTerminal.run", "args": {"name": "b"}} ] 65 | }, 66 | { 67 | "before": ["", "r", "s"], "after": [], 68 | "commands": [ {"command": "runInTerminal.run", "args": {"name": "s"}} ] 69 | } 70 | ], 71 | "runInTerminal.commands": [ 72 | {"match": "_spec\\.rb$", "name": "l", "cmd": "./bin/rspec ${relativeFile}:${line}"}, 73 | {"match": "_spec\\.rb$", "name": "b", "cmd": "./bin/rspec ${relativeFile}"}, 74 | {"match": "_spec\\.rb$", "name": "s", "cmd": "./bin/rspec"}, 75 | {"match": "(spec|test)\\.js$", "name": "b", "cmd": "xvfb-run ./node_modules/karma/bin/karma start --single-run=true --single-file=\"${relativeFile}\""} 76 | ], 77 | ... 78 | ``` 79 | 80 | Note above, you specify each keybinding in your `vim` settings only once with a target `name`, and then in your `runInTerminal.commands`, you can specify multiple commands with the same `name` but different file `match` expressions. In this case, ` r b` maps to the `name` 'b', which has a command for `ruby` and for `javascript`. 81 | 82 | ### 'runInTerminal.clearBeforeRun' 83 | 84 | ``` 85 | ... 86 | "runInTerminal.clearBeforeRun": false, // defaults false 87 | ... 88 | ``` 89 | 90 | ## Substitution Tokens 91 | 92 | You can use the following substitution tokens in `cmd` strings: 93 | 94 | - `${column}` 95 | - `${cwd}` 96 | - `${env.Name}` (replace environment variables) 97 | - `${file}` 98 | - `${fileBasename}` 99 | - `${fileBasenameNoExt}` 100 | - `${fileDirname}` 101 | - `${fileExtname}` 102 | - `${line}` 103 | - `${relativeFile}` 104 | - `${workspaceRoot}` 105 | 106 | ## Commands 107 | 108 | ### `runInTerminal.run` 109 | 110 | You should provide an `object` as the value of the `arguments` key when calling this command. This object must have **either** (i) a `name` pointing to a command in `runInTerminal.commands` **or** (ii) a file `match` expression **and** `cmd` to execute. 111 | 112 | (i) 113 | 114 | ``` 115 | ... 116 | "command": "runInTerminal.run", 117 | "args": {"name": "focused"}, 118 | ... 119 | ``` 120 | 121 | (ii) 122 | 123 | ``` 124 | ... 125 | "command": "runInTerminal.run", 126 | "args": {"match": "\\.py$", "cmd": "/usr/bin/env python ${relativeFile}"}, 127 | ... 128 | ``` 129 | 130 | ### `runInTerminal.runLast` 131 | 132 | Runs the last `cmd` run by `runInTerminal.run` again. 133 | 134 | ## Example of a Javascript / Typescript Line Runner 135 | 136 | Here is an example of a VS Code extension, written in typescript, with a line runner bound to `,rl`: 137 | 138 | The `settings.json`: 139 | https://github.com/kortina/vscode-markdown-notes/blob/ba40b5cea0d10873b571e3e91d453dd7119cb89d/.vscode/settings.json#L8 140 | 141 | A shell script that finds the jest bdd description given a `file:lineno`: 142 | https://github.com/kortina/vscode-markdown-notes/blob/ba40b5cea0d10873b571e3e91d453dd7119cb89d/jest-focused.sh 143 | 144 | ## More Example Settings 145 | 146 | My `runInTerminal.commands` in `settings.json`: 147 | https://github.com/kortina/dotfiles/blob/f9465ac8dd82738b3f7a4415dd0c49412c86f841/vscode/settings.json#L68 148 | 149 | My `vim.normalModeKeyBindingsNonRecursive` bindings to `runInTerminal` in `settings.json`: 150 | https://github.com/kortina/dotfiles/blob/f9465ac8dd82738b3f7a4415dd0c49412c86f841/vscode/settings.json#L181 151 | 152 | ## Known Issues 153 | 154 | - The `${relativeFile}` substitution token only works when you have opened an entire folder with `vscode`, not a single file. 155 | - Unknown behavior when many commands in 'runInTerminal.commands' match both the `match` expression and `name` of the command run. 156 | 157 | ## Development and Release 158 | 159 | To run locally 160 | ```sh 161 | # install deps 162 | npm install --dev 163 | ``` 164 | 165 | To create a new release, 166 | 167 | ```sh 168 | # bump version number in package.json 169 | npm run vpackage # package the release, creates vsix 170 | npm run vpublish # publish to store, see https://code.visualstudio.com/api/working-with-extensions/publishing-extension 171 | # Will prompt for Azure Devops Personal Access Token, get fresh one at: 172 | # https://dev.azure.com/andrewkortina/ 173 | # On "Error: Failed Request: Unauthorized(401)" 174 | # see: https://github.com/Microsoft/vscode-vsce/issues/11 175 | # The reason for returning 401 was that I didn't set the Accounts setting to all accessible accounts. 176 | ``` 177 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* global console, process */ 3 | /* eslint-disable no-console */ 4 | import * as vscode from 'vscode'; 5 | import * as path from 'path'; 6 | 7 | interface Args { 8 | // eslint-disable-line no-undef 9 | name?: string; // eslint-disable-line no-undef 10 | match?: string; // eslint-disable-line no-undef 11 | cmd?: string; // eslint-disable-line no-undef 12 | } 13 | 14 | var LAST_COMMAND: Args | null = null; 15 | 16 | // Static class that creates and holds a reference to a terminal and can run commands in it. 17 | class Term { 18 | static termName: string = 'run-in-terminal'; // eslint-disable-line no-undef 19 | static term: vscode.Terminal; // eslint-disable-line no-undef 20 | static processId: number; // eslint-disable-line no-undef 21 | 22 | static _term() { 23 | if (!Term.term) { 24 | Term.term = vscode.window.createTerminal(Term.termName); 25 | Term.term.show(true); 26 | Term.term.processId.then((procId) => { 27 | Term.processId = procId; 28 | }); 29 | 30 | // if user closes the terminal, delete our reference: 31 | vscode.window.onDidCloseTerminal((event) => { 32 | event.processId.then((procId) => { 33 | if (procId == Term.processId) { 34 | Term.term = undefined; 35 | Term.processId = undefined; 36 | } 37 | }); 38 | }); 39 | } 40 | return Term.term; 41 | } 42 | 43 | static run(command: string) { 44 | console.log(`Running ${command} in ${JSON.stringify(Term._term())}`); 45 | Term._term().sendText(command, true); 46 | } 47 | 48 | static dispose() { 49 | if (Term._term()) { 50 | Term._term().dispose(); 51 | Term.term = undefined; 52 | } 53 | } 54 | } 55 | 56 | class Cmd { 57 | name: string | null; // eslint-disable-line no-undef 58 | match: string | null; // eslint-disable-line no-undef 59 | cmd: string | null; // eslint-disable-line no-undef 60 | editor: vscode.TextEditor; // eslint-disable-line no-undef 61 | config: vscode.WorkspaceConfiguration; // eslint-disable-line no-undef 62 | 63 | constructor( 64 | editor: vscode.TextEditor, 65 | config: vscode.WorkspaceConfiguration, 66 | name?: string, 67 | match?: string, 68 | cmd?: string 69 | ) { 70 | this.name = name || null; 71 | this.match = match || null; 72 | this.cmd = cmd || null; 73 | this.editor = editor; 74 | this.config = config; 75 | } 76 | 77 | private isMatch(pattern: string): boolean { 78 | try { 79 | return pattern.length > 0 && new RegExp(pattern).test(this.editor.document.fileName); 80 | } catch (e) { 81 | console.log(e.stack); 82 | showError(`invalid match pattern: ${pattern}`); 83 | return false; 84 | } 85 | } 86 | 87 | public findCmd(): string | undefined { 88 | console.log('findCmd this.match', this.match, 'this.cmd', this.cmd, 'this.name', this.name); 89 | if (this.match && this.cmd && this.isMatch(this.match)) { 90 | return this.cmd; 91 | } else if (this.name) { 92 | var that = this; 93 | var all = this.config.inspect('commands'); 94 | var finder = (c: Args) => { 95 | console.log('find', JSON.stringify(c), 'c.cmd', `${c.cmd}`); 96 | return c.name == that.name && that.isMatch(c.match) && `${c.cmd}` != ''; 97 | }; 98 | // look through commands configurations from most specific to least. 99 | // precedence defined at: https://code.visualstudio.com/api/references/vscode-api#WorkspaceConfiguration 100 | var needle = 101 | ((all.workspaceFolderValue || []) as Args[]).find(finder) || 102 | ((all.workspaceValue || []) as Args[]).find(finder) || 103 | ((all.globalValue || []) as Args[]).find(finder) || 104 | ((all.defaultValue || []) as Args[]).find(finder); 105 | if (needle) { 106 | return needle.cmd; 107 | } 108 | } 109 | return undefined; 110 | } 111 | 112 | public build(command: string): string { 113 | var extName = path.extname(this.editor.document.fileName); 114 | var relativeFile = '.' + this.editor.document.fileName.replace(vscode.workspace.rootPath, ''); 115 | var line = this.editor.selection.active.line + 1; 116 | var column = this.editor.selection.active.character + 1; 117 | 118 | command = command.replace(/\${line}/g, `${line}`); 119 | command = command.replace(/\${column}/g, `${column}`); 120 | command = command.replace(/\${relativeFile}/g, relativeFile); 121 | command = command.replace(/\${file}/g, `${this.editor.document.fileName}`); 122 | command = command.replace(/\${workspaceRoot}/g, `${vscode.workspace.rootPath}`); 123 | command = command.replace( 124 | /\${fileBasename}/g, 125 | `${path.basename(this.editor.document.fileName)}` 126 | ); 127 | command = command.replace(/\${fileDirname}/g, `${path.dirname(this.editor.document.fileName)}`); 128 | command = command.replace(/\${fileExtname}/g, `${extName}`); 129 | command = command.replace( 130 | /\${fileBasenameNoExt}/g, 131 | `${path.basename(this.editor.document.fileName, extName)}` 132 | ); 133 | command = command.replace(/\${cwd}/g, `${process.cwd()}`); 134 | 135 | if (this.config.get('saveCommandInHistory')) { 136 | command = this.config.get('clearBeforeRun') ? `clear; ${command}` : command; 137 | } else { 138 | command = this.config.get('clearBeforeRun') ? ` clear; ${command}` : ` ${command}`; 139 | } 140 | 141 | // replace environment variables ${env.Name} 142 | command = command.replace(/\${env\.([^}]+)}/g, (sub, envName) => { 143 | return process.env[envName]; 144 | }); 145 | 146 | return command; 147 | } 148 | } 149 | 150 | export function isMatch(pattern: string, fileName: string): boolean { 151 | console.log('isMatch pattern', pattern, 'fileName', fileName); 152 | try { 153 | return pattern.length > 0 && new RegExp(pattern).test(fileName); 154 | } catch (e) { 155 | console.log(e.stack); 156 | showError(`invalid match pattern: ${pattern}`); 157 | return false; 158 | } 159 | } 160 | 161 | function showError(msg: string): void { 162 | vscode.window.showErrorMessage(`run-in-terminal: ${msg}`); 163 | } 164 | 165 | function runCommand(editor: vscode.TextEditor, args?: Args) { 166 | if (!editor) { 167 | console.log('run-in-terminal: no editor.'); 168 | return; 169 | } 170 | if (!args) { 171 | console.log('run-in-terminal: no args.'); 172 | return; 173 | } 174 | var a: Args = args; 175 | LAST_COMMAND = a; 176 | console.log(`run-int-terminal: ${JSON.stringify(a)}`); 177 | 178 | var cfg = vscode.workspace.getConfiguration('runInTerminal'); 179 | console.log('inspect', JSON.stringify(cfg.inspect('commands'))); 180 | var cmd = new Cmd(editor, cfg, a.name, a.match, a.cmd); 181 | var cmdStr = cmd.findCmd(); 182 | 183 | if (!cmdStr) { 184 | console.log(`run-in-terminal: no command found for args: ${JSON.stringify(a)}`); 185 | return; 186 | } 187 | Term.run(cmd.build(cmdStr)); 188 | } 189 | 190 | // vscode.extensions API 191 | export function activate(context: vscode.ExtensionContext) { 192 | // Use the console to output diagnostic information (console.log) and errors (console.error) 193 | // This line of code will only be executed once when your extension is activated 194 | console.log('activate runInTerminal'); 195 | 196 | // The commandId parameter must match the command field in package.json 197 | let disposable = vscode.commands.registerCommand('runInTerminal.run', (args?: Args) => { 198 | runCommand(vscode.window.activeTextEditor, args); 199 | }); 200 | context.subscriptions.push(disposable); 201 | 202 | // The commandId parameter must match the command field in package.json 203 | disposable = vscode.commands.registerCommand('runInTerminal.runLast', () => { 204 | if (LAST_COMMAND) { 205 | runCommand(vscode.window.activeTextEditor, LAST_COMMAND); 206 | } 207 | }); 208 | context.subscriptions.push(disposable); 209 | } 210 | 211 | // vscode.extensions API 212 | export function deactivate() { 213 | Term.dispose(); 214 | } 215 | --------------------------------------------------------------------------------