├── .gitignore ├── .vscode ├── extensions.json ├── tasks.json ├── settings.json └── launch.json ├── .vscodeignore ├── CHANGELOG.md ├── tslint.json ├── src ├── test │ ├── suite │ │ ├── extension.test.ts │ │ └── index.ts │ └── runTest.ts └── extension.ts ├── tsconfig.json ├── README.md ├── package.json └── vsc-extension-quickstart.md /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | .vscode-test/ 4 | *.vsix 5 | npm-debug.log -------------------------------------------------------------------------------- /.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 | ] 6 | } -------------------------------------------------------------------------------- /.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 -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to the "jump-to-alternative" extension will be documented in this file. 4 | 5 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. 6 | 7 | ## [Unreleased] 8 | 9 | - Initial release -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /src/test/suite/extension.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import { before } from 'mocha'; 3 | 4 | // You can import and use all API from the 'vscode' module 5 | // as well as import your extension to test it 6 | import * as vscode from 'vscode'; 7 | // import * as myExtension from '../extension'; 8 | 9 | suite('Extension Test Suite', () => { 10 | before(() => { 11 | vscode.window.showInformationMessage('Start all tests.'); 12 | }); 13 | 14 | test('Sample test', () => { 15 | assert.equal(-1, [1, 2, 3].indexOf(5)); 16 | assert.equal(-1, [1, 2, 3].indexOf(0)); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /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/runTest.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | 3 | import { runTests } from 'vscode-test'; 4 | 5 | async function main() { 6 | try { 7 | // The folder containing the Extension Manifest package.json 8 | // Passed to `--extensionDevelopmentPath` 9 | const extensionDevelopmentPath = path.resolve(__dirname, '../../'); 10 | 11 | // The path to test runner 12 | // Passed to --extensionTestsPath 13 | const extensionTestsPath = path.resolve(__dirname, './suite/index'); 14 | 15 | // Download VS Code, unzip it and run the integration test 16 | await runTests({ extensionDevelopmentPath, extensionTestsPath }); 17 | } catch (err) { 18 | console.error('Failed to run tests'); 19 | process.exit(1); 20 | } 21 | } 22 | 23 | main(); 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jump-to-alternative README 2 | 3 | This extension registers 1 command that will toggle view between source file and test file associated with it. 4 | 5 | ## Features 6 | 7 | * When the command is issued, your editor will jump to the alternative file if the extension is able to find one. 8 | * It will try to find file with the same name as close to the current one as possible within subtree. 9 | * Press `ALT` + `T` to jump between files 10 | 11 | ## Release Notes 12 | 13 | Experimental 14 | 15 | ----------------------------------------------------------------------------------------------------------- 16 | 17 | ### Acknowledgements 18 | 19 | This extension is based on Blake Herrington's [vscode-jump-to-test](https://github.com/blakeherrington/vscode-jump-to-test). 20 | It was cloned instead of forked because It needs to be published for my usecases 21 | 22 | **Enjoy!** 23 | -------------------------------------------------------------------------------- /src/test/suite/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as Mocha from 'mocha'; 3 | import * as glob from 'glob'; 4 | 5 | export function run(): Promise { 6 | // Create the mocha test 7 | const mocha = new Mocha({ 8 | ui: 'tdd', 9 | color: true 10 | }); 11 | 12 | const testsRoot = path.resolve(__dirname, '..'); 13 | 14 | return new Promise((c, e) => { 15 | glob('**/**.test.js', { cwd: testsRoot }, (err, files) => { 16 | if (err) { 17 | return e(err); 18 | } 19 | 20 | // Add files to the test suite 21 | files.forEach(f => mocha.addFile(path.resolve(testsRoot, f))); 22 | 23 | try { 24 | // Run the mocha test 25 | mocha.run(failures => { 26 | if (failures > 0) { 27 | e(new Error(`${failures} tests failed.`)); 28 | } else { 29 | c(); 30 | } 31 | }); 32 | } catch (err) { 33 | e(err); 34 | } 35 | }); 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /.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": [ 14 | "--extensionDevelopmentPath=${workspaceFolder}" 15 | ], 16 | "outFiles": [ 17 | "${workspaceFolder}/out/**/*.js" 18 | ], 19 | "preLaunchTask": "npm: watch" 20 | }, 21 | { 22 | "name": "Extension Tests", 23 | "type": "extensionHost", 24 | "request": "launch", 25 | "runtimeExecutable": "${execPath}", 26 | "args": [ 27 | "--extensionDevelopmentPath=${workspaceFolder}", 28 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" 29 | ], 30 | "outFiles": [ 31 | "${workspaceFolder}/out/test/**/*.js" 32 | ], 33 | "preLaunchTask": "npm: watch" 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jump-to-alternative", 3 | "displayName": "Jump to alternative file", 4 | "description": "Jump between source and test files with ease", 5 | "publisher": "vladimir-penkin", 6 | "version": "0.1.0", 7 | "engines": { 8 | "vscode": "^1.37.0" 9 | }, 10 | "author": { 11 | "name": "Vladimir Penkin" 12 | }, 13 | "categories": [ 14 | "Other" 15 | ], 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/shell/vscode-jump-to-alternative.git" 19 | }, 20 | "homepage": "https://github.com/shell/vscode-jump-to-alternative/blob/master/README.md", 21 | "activationEvents": [ 22 | "onCommand:extension.jumpToAlternative" 23 | ], 24 | "main": "./out/extension.js", 25 | "contributes": { 26 | "commands": [ 27 | { 28 | "command": "extension.jumpToAlternative", 29 | "title": "Jump to alternative file" 30 | } 31 | ], 32 | "keybindings": [ 33 | { 34 | "command": "extension.jumpToAlternative", 35 | "key": "alt+t", 36 | "mac": "alt+t", 37 | "when": "editorTextFocus" 38 | } 39 | ] 40 | }, 41 | "scripts": { 42 | "vscode:prepublish": "npm run compile", 43 | "compile": "tsc -p ./", 44 | "watch": "tsc -watch -p ./", 45 | "pretest": "npm run compile", 46 | "test": "node ./out/test/runTest.js" 47 | }, 48 | "devDependencies": { 49 | "@types/glob": "^7.1.1", 50 | "@types/mocha": "^9.1.0", 51 | "@types/node": "^17.0.23", 52 | "@types/vscode": "^1.43.0", 53 | "glob": "^7.1.6", 54 | "mocha": "^9.2.2", 55 | "tslint": "^6.1.3", 56 | "typescript": "^4.6.2", 57 | "vscode-test": "^1.3.0" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /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 | 25 | ## Explore the API 26 | 27 | * You can open the full set of our API when you open the file `node_modules/@types/vscode/index.d.ts`. 28 | 29 | ## Run tests 30 | 31 | * Open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Extension Tests`. 32 | * Press `F5` to run the tests in a new window with your extension loaded. 33 | * See the output of the test result in the debug console. 34 | * Make changes to `src/test/suite/extension.test.ts` or create new test files inside the `test/suite` folder. 35 | * The provided test runner will only consider files matching the name pattern `**.test.ts`. 36 | * You can create folders inside the `test` folder to structure your tests any way you want. 37 | 38 | ## Go further 39 | 40 | * Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/bundling-extension). 41 | * [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VSCode extension marketplace. 42 | * Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration). 43 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | // The module 'vscode' contains the VS Code extensibility API 2 | // Import the module and reference it with the alias vscode in your code below 3 | import * as vscode from 'vscode'; 4 | import * as path from 'path'; 5 | 6 | // this method is called when your extension is activated 7 | // your extension is activated the very first time the command is executed 8 | export function activate(context: vscode.ExtensionContext) { 9 | // The command has been defined in the package.json file 10 | // Now provide the implementation of the command with registerCommand 11 | // The commandId parameter must match the command field in package.json 12 | let disposable = vscode.commands.registerCommand('extension.jumpToAlternative', () => { 13 | if (!vscode.window.activeTextEditor) { 14 | return; 15 | } 16 | 17 | const basePath = vscode.window.activeTextEditor.document.uri.path; 18 | const splitFileName = path.basename(vscode.window.activeTextEditor.document.fileName).split("."); 19 | const fileName = splitFileName.slice(0,-1).join(''); 20 | const extension = splitFileName[splitFileName.length-1]; 21 | 22 | let globPattern = `**/${fileName}`; 23 | globPattern = determineGlobPattern(globPattern, extension); 24 | 25 | vscode.workspace.findFiles(globPattern, '**/node_modules/**', 10).then((found: vscode.Uri[]): void => { 26 | if (found.length === 0) { 27 | vscode.window.setStatusBarMessage('jumpToAlternative: Unable to find test file', 3000); 28 | return; 29 | } 30 | 31 | const closestFile = found.sort((a, b) => 32 | getPathDistance(basePath, b.path) - getPathDistance(basePath, a.path))[0]; 33 | 34 | vscode.commands.executeCommand('vscode.open', vscode.Uri.file(closestFile.path)); 35 | }); 36 | 37 | function getPathDistance(a: string, b: string): number { 38 | let dist = 0; 39 | [...a].forEach((char, idx) => { 40 | if (idx < b.length && char === b[idx]) { 41 | dist++; 42 | } 43 | }); 44 | 45 | return dist; 46 | } 47 | 48 | function determineGlobPattern(globPattern: string, extension: string): string { 49 | if (globPattern.toLowerCase().includes('spec') || globPattern.toLowerCase().includes('test')) { 50 | globPattern = handleSwitchToSourceFile(globPattern); 51 | } else { 52 | globPattern = handleSwitchToTestFile(globPattern); 53 | } 54 | 55 | return `${globPattern}.${extension}`; 56 | } 57 | 58 | function handleSwitchToSourceFile(globPattern: string): string { 59 | globPattern = globPattern.replace(new RegExp(`[_.]*spec`, 'ig'), ''); 60 | globPattern = globPattern.replace(new RegExp(`[_.]*test`, 'ig'), ''); 61 | 62 | return globPattern; 63 | } 64 | 65 | function handleSwitchToTestFile(globPattern: string): string { 66 | return `${globPattern}{_,.}{spec,test,Spec,Test}`; 67 | } 68 | }); 69 | 70 | context.subscriptions.push(disposable); 71 | } 72 | 73 | // this method is called when your extension is deactivated 74 | export function deactivate() {} 75 | --------------------------------------------------------------------------------