├── .gitignore ├── conda-env.yaml ├── images └── vscode-terminal-capture-clipboard.gif ├── .vscodeignore ├── .vscode ├── extensions.json ├── tasks.json ├── settings.json └── launch.json ├── CHANGELOG.md ├── tslint.json ├── tsconfig.json ├── src ├── test │ ├── extension.test.ts │ └── index.ts └── extension.ts ├── package.json ├── DevNotes.md ├── vsc-extension-quickstart.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | out/ 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /conda-env.yaml: -------------------------------------------------------------------------------- 1 | name: vscode-dev 2 | channels: 3 | - conda-forge 4 | - javascript 5 | - defaults 6 | dependencies: 7 | - nodejs>=11.9.0 8 | 9 | -------------------------------------------------------------------------------- /images/vscode-terminal-capture-clipboard.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikekwright/vscode-terminal-capture/HEAD/images/vscode-terminal-capture-clipboard.gif -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/test/** 4 | src/** 5 | .gitignore 6 | vsc-extension-quickstart.md 7 | **/tsconfig.json 8 | **/tslint.json 9 | **/*.map 10 | **/*.ts -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "ms-vscode.vscode-typescript-tslint-plugin" 6 | ] 7 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to the "vscode-terminal-capture" extension will be documented in this file. 3 | 4 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. 5 | 6 | ## [0.1.0] 7 | - Initial release 8 | 9 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-string-throw": true, 4 | "no-unused-expression": true, 5 | "no-duplicate-variable": true, 6 | "curly": true, 7 | "class-name": true, 8 | "semicolon": [ 9 | true, 10 | "always" 11 | ], 12 | "triple-equals": true 13 | }, 14 | "defaultSeverity": "warning" 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 | } 21 | -------------------------------------------------------------------------------- /.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 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 10 | "typescript.tsc.autoDetect": "off" 11 | } -------------------------------------------------------------------------------- /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 | "strict": true /* enable all strict type-checking options */ 12 | /* Additional Checks */ 13 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 14 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 15 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 16 | }, 17 | "exclude": [ 18 | "node_modules", 19 | ".vscode-test" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /src/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 '../extension'; 13 | 14 | // Defines a Mocha test suite to group tests of similar kind together 15 | suite("Extension Tests", function () { 16 | 17 | // Defines a Mocha unit test 18 | test("Something 1", function() { 19 | assert.equal(-1, [1, 2, 3].indexOf(5)); 20 | assert.equal(-1, [1, 2, 3].indexOf(0)); 21 | }); 22 | }); -------------------------------------------------------------------------------- /src/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 | import * as testRunner from 'vscode/lib/testrunner'; 14 | 15 | // You can directly control Mocha options by configuring the test runner below 16 | // See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options 17 | // for more info 18 | testRunner.configure({ 19 | ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.) 20 | useColors: true // colored output from test results 21 | }); 22 | 23 | module.exports = testRunner; -------------------------------------------------------------------------------- /.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 | "name": "Run Extension", 9 | "type": "extensionHost", 10 | "request": "launch", 11 | "runtimeExecutable": "${execPath}", 12 | "args": [ 13 | "--extensionDevelopmentPath=${workspaceFolder}" 14 | ], 15 | "outFiles": [ 16 | "${workspaceFolder}/out/**/*.js" 17 | ], 18 | "preLaunchTask": "npm: watch" 19 | }, 20 | { 21 | "name": "Extension Tests", 22 | "type": "extensionHost", 23 | "request": "launch", 24 | "runtimeExecutable": "${execPath}", 25 | "args": [ 26 | "--extensionDevelopmentPath=${workspaceFolder}", 27 | "--extensionTestsPath=${workspaceFolder}/out/test" 28 | ], 29 | "outFiles": [ 30 | "${workspaceFolder}/out/test/**/*.js" 31 | ], 32 | "preLaunchTask": "npm: watch" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vscode-terminal-capture", 3 | "displayName": "Terminal Capture", 4 | "description": " Take an open terminal's output and open it in a tab to quickly navigate and edit the output.", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/mikekwright/vscode-terminal-capture" 8 | }, 9 | "version": "0.0.1", 10 | "publisher": "devwright", 11 | "engines": { 12 | "vscode": "^1.31.0" 13 | }, 14 | "categories": [ 15 | "Other" 16 | ], 17 | "activationEvents": [ 18 | "*" 19 | ], 20 | "main": "./out/extension.js", 21 | "contributes": { 22 | "commands": [ 23 | { 24 | "command": "extension.terminalCapture.runCapture", 25 | "title": "Terminal: Capture" 26 | } 27 | ], 28 | "configuration": { 29 | "title": "Terminal Capture", 30 | "type": "object", 31 | "properties": { 32 | "terminalCapture.enable": { 33 | "type": "boolean", 34 | "description": "If false, disable the terminal capture extension", 35 | "default": true 36 | }, 37 | "terminalCapture.useClipboard": { 38 | "type": "boolean", 39 | "description": "If false, use the cache mode that tracks all history", 40 | "default": true 41 | } 42 | } 43 | } 44 | }, 45 | "scripts": { 46 | "vscode:prepublish": "npm run compile", 47 | "compile": "tsc -p ./", 48 | "watch": "tsc -watch -p ./", 49 | "postinstall": "node ./node_modules/vscode/bin/install", 50 | "test": "npm run compile && node ./node_modules/vscode/bin/test" 51 | }, 52 | "devDependencies": { 53 | "typescript": "^3.3.1", 54 | "vscode": "^1.1.28", 55 | "tslint": "^5.12.1", 56 | "@types/node": "^10.12.21", 57 | "@types/mocha": "^2.2.42" 58 | }, 59 | "dependencies": { 60 | "vsce": "^1.57.0" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /DevNotes.md: -------------------------------------------------------------------------------- 1 | Dev Notes 2 | ============================================== 3 | 4 | ## Ideas 5 | 6 | Here is the flow that I would like to see if I can get access to the underlying terminal content... 7 | **NOTE:** The terminal is based on `xterm.js` - https://github.com/xtermjs/xterm.js#readme 8 | I also think that getting access to the `Buffer` might make this solution a bit easier 9 | 10 | **NOTE**: This code was all found on commit `c110d84460b3e45842a8fe753562341003595e1d` from the vscode source. 11 | 12 | 1. `terminalInstance.selectAll()` - `src/vs/workbench/contrib/terminal/electron-browser/terminalActions.ts:185` 13 | 2. `terminalInstance.copySelection()` - `src/vs/workbench/contrib/terminal/electron-browser/terminalActions.ts:164` 14 | `this._xterm.getSelection()` - `src/vs/workbench/contrib/terminal/electron-browser/terminalInstance.ts:684` 15 | 3. `terminalInstance.clearSelection()` - `src/vs/workbench/contrib/terminal/electron-browser/terminalActions.ts:164` 16 | `this._xterm.clearSelection()` - `src/vs/workbench/contrib/terminal/electron-browser/terminalInstance.ts:695` 17 | 18 | ## Clearning Special Characters 19 | 20 | One of the first issues that I ran into is that the dat captured from the 21 | terminal included special characters (like color selections). 22 | 23 | This regex can be used in js to select all color code special characters so 24 | that they can be cleaned from the data. 25 | 26 | ```\x1b\[[0-9;]*m``` 27 | 28 | ## External Pages 29 | 30 | * VSCode extension getting started - [https://code.visualstudio.com/api/get-started/your-first-extension](https://code.visualstudio.com/api/get-started/your-first-extension) 31 | * `xterm.js` site - [https://xtermjs.org/](https://xtermjs.org) 32 | * Terminal Sample code for vscode api - [https://github.com/Microsoft/vscode-extension-samples/blob/master/terminal-sample/src/extension.ts](https://github.com/Microsoft/vscode-extension-samples/blob/master/terminal-sample/src/extension.ts) 33 | * Using the proposed api (Never did) - [https://code.visualstudio.com/api/advanced-topics/using-proposed-api](https://code.visualstudio.com/api/advanced-topics/using-proposed-api) 34 | 35 | 36 | -------------------------------------------------------------------------------- /vsc-extension-quickstart.md: -------------------------------------------------------------------------------- 1 | # Welcome to your VS Code Extension 2 | 3 | ## What's in the folder 4 | 5 | * This folder contains all of the files necessary for your extension. 6 | * `package.json` - this is the manifest file in which you declare your extension and command. 7 | * The sample plugin registers a command and defines its title and command name. With this information 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 activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`. 10 | * We pass the function containing the implementation of the command as the second parameter to `registerCommand`. 11 | 12 | ## Get up and running straight away 13 | 14 | * Press `F5` to open a new window with your extension loaded. 15 | * Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`. 16 | * Set breakpoints in your code inside `src/extension.ts` to debug your extension. 17 | * Find output from your extension in the debug console. 18 | 19 | ## Make changes 20 | 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 | 26 | * You can open the full set of our API when you open the file `node_modules/vscode/vscode.d.ts`. 27 | 28 | ## Run tests 29 | 30 | * Open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Extension Tests`. 31 | * Press `F5` to run the tests in a new window with your extension loaded. 32 | * See the output of the test result in the debug console. 33 | * Make changes to `test/extension.test.ts` or create new test files inside the `test` folder. 34 | * By convention, the test runner will only consider files matching the name pattern `**.test.ts`. 35 | * You can create folders inside the `test` folder to structure your tests any way you want. 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | VSCode - Terminal Capture 2 | ================================================= 3 | 4 | As a vim user, one of the features that I truly enjoy is the integrated 5 | terminal and the ability to switch to `Terminal-Normal` and quickly scroll 6 | through the output of my terminal. As such I wanted a quick and easy way 7 | to do something simliar in vscode (including using vi keys to navigate/interact 8 | with the terminal). 9 | 10 | The first thing that I tried was using tmux in the terminal and start it by 11 | default when in a vscode environment. While this worked, I didn't like the 12 | flow quite as much. So I decided to try my hand at writing a vscode extension. 13 | 14 | ## Features 15 | 16 | Take the output from the last active terminal and dump it into a new file. 17 | 18 | ![Capture using Clipboard](images/vscode-terminal-capture-clipboard.gif) 19 | 20 | There are currently two modes that the extension can run in. 21 | 22 | 1. clipboard mode 23 | 2. caching mode (**alpha** mode) 24 | 25 | When using clipboard mode, the extension functions just like a macro where it 26 | quickly runs the following vscode commands. 27 | 28 | * `workbench.action.terminal.selectAll` 29 | * `workbench.action.terminal.copySelection` 30 | * `workbench.action.terminal.clearSelection` 31 | * `workbench.action.files.newUntitledFile` 32 | * `editor.action.clipboardPasteAction` 33 | 34 | The caching mode system will track all data entered into the terminal (including 35 | keypresses like backspace) and display all the captured content. Right now this 36 | means that it will display all the special characters including `\b` and `\r`, etc. 37 | This is nice in that it can capture more of the history of how the terminal was run, 38 | however it will also use more memory as it is caching the output for the life of 39 | the terminal. 40 | 41 | ### Adding a Keybinding 42 | 43 | By default, this extension does not introduce a keybinding as we don't want 44 | to introduce a conflict or other confusion to your workflow. It you would 45 | like to setup this command to a keybinding, here is an example of the json 46 | you can enter in the `keybindings.json` file. 47 | 48 | { 49 | "key": "ctrl+t c", 50 | "command": "extension.terminalCapture.runCapture", 51 | "when": "terminalFocus" 52 | } 53 | 54 | ## Extension Settings 55 | 56 | This extension has a couple of settings depending on the type of usage you would 57 | like to have. 58 | 59 | * `terminalCaptuer.enable`: enable/disable this extension 60 | * `terminalCapture.useClipboard`: use/disable clipboard copy mode 61 | 62 | This extension has a single command. 63 | 64 | * **Terminal: Capture Output** - `extension.terminalCapture.runCapture` 65 | 66 | ## Known Issues 67 | 68 | At this time, it is known that the caching mode will include style characters 69 | in the output. This is to be fixed soon. 70 | 71 | ### 0.1.0 72 | 73 | For this version, this is the initial release that supports using the clipboard to 74 | copy content from the terminal to an unamed file. 75 | 76 | The cache mode is partially supported, but does not handle all special characters 77 | correctly. This is currently not the recommended mode. 78 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | // Common data to be used elsewhere 4 | let terminalData = {}; 5 | 6 | export function activate(context: vscode.ExtensionContext) { 7 | let options = vscode.workspace.getConfiguration('terminalCapture'); 8 | terminalData = {}; 9 | 10 | if (options.get('enable') === false) { 11 | console.log('Terminal Capture is disabled'); 12 | return; 13 | } 14 | 15 | console.log('Terminal Capture extension is now active'); 16 | 17 | if (options.get('useClipboard') === false) { 18 | vscode.window.terminals.forEach(t => { 19 | registerTerminalForCapture(t); 20 | }); 21 | 22 | vscode.window.onDidOpenTerminal(t=> { 23 | registerTerminalForCapture(t); 24 | }); 25 | } 26 | 27 | context.subscriptions.push(vscode.commands.registerCommand('extension.terminalCapture.runCapture', () => { 28 | if (options.get('enable') === false) { 29 | console.log('Command has been disabled, not running'); 30 | } 31 | 32 | const terminals = (vscode.window).terminals; 33 | if (terminals.length <= 0) { 34 | vscode.window.showWarningMessage('No terminals found, cannot run copy'); 35 | return; 36 | } 37 | 38 | if (options.get('useClipboard') === true) { 39 | runClipboardMode(); 40 | } else { 41 | runCacheMode(); 42 | } 43 | })); 44 | } 45 | 46 | export function deactivate() { 47 | terminalData = {}; 48 | } 49 | 50 | 51 | function runCacheMode() { 52 | let terminal = vscode.window.activeTerminal; 53 | if (terminal === undefined) { 54 | vscode.window.showWarningMessage('No active terminal found, can not capture'); 55 | return; 56 | } 57 | 58 | terminal.processId.then(terminalId => { 59 | vscode.commands.executeCommand('workbench.action.files.newUntitledFile').then(() => { 60 | let editor = vscode.window.activeTextEditor; 61 | if (editor === undefined) { 62 | vscode.window.showWarningMessage('Failed to find active editor to paste terminal content'); 63 | return; 64 | } 65 | 66 | let cache = cleanupCacheData((terminalData)[terminalId]); 67 | editor.edit(builder => { 68 | builder.insert(new vscode.Position(0, 0), cache); 69 | }); 70 | }); 71 | }); 72 | } 73 | 74 | 75 | function runClipboardMode() { 76 | vscode.commands.executeCommand('workbench.action.terminal.selectAll').then(() => { 77 | vscode.commands.executeCommand('workbench.action.terminal.copySelection').then(() => { 78 | vscode.commands.executeCommand('workbench.action.terminal.clearSelection').then(() => { 79 | vscode.commands.executeCommand('workbench.action.files.newUntitledFile').then(() => { 80 | vscode.commands.executeCommand('editor.action.clipboardPasteAction'); 81 | }); 82 | }); 83 | }); 84 | }); 85 | } 86 | 87 | 88 | function cleanupCacheData(data: string): string { 89 | return data.replace(new RegExp('\x1b\[[0-9;]*m', 'g'), ''); 90 | } 91 | 92 | function registerTerminalForCapture(terminal: vscode.Terminal) { 93 | terminal.processId.then(terminalId => { 94 | (terminalData)[terminalId] = ""; 95 | (terminal).onDidWriteData((data: any) => { 96 | // TODO: 97 | // - Need to remove (or handle) backspace 98 | // - not sure what to do about carriage return??? 99 | // - might have some odd output 100 | (terminalData)[terminalId] += data; 101 | }); 102 | }); 103 | } 104 | --------------------------------------------------------------------------------