├── .eslintrc.json ├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── images ├── demo.gif ├── icon.png └── selection.png ├── package-lock.json ├── package.json ├── src ├── AnnotateCodeLensProvider.ts ├── AnnotateController.ts ├── Annotator.ts ├── countSelection.ts ├── extension.ts └── test │ ├── runTest.ts │ └── suite │ ├── extension.test.ts │ └── index.ts └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { 5 | "ecmaVersion": 6, 6 | "sourceType": "module" 7 | }, 8 | "extends": [ 9 | "eslint:recommended", 10 | "plugin:@typescript-eslint/recommended", 11 | "plugin:prettier/recommended", 12 | "plugin:import/errors", 13 | "plugin:import/warnings", 14 | "plugin:import/typescript" 15 | ], 16 | "rules": { 17 | "prettier/prettier": "error", 18 | "import/no-unresolved": [ 19 | "warn", 20 | { 21 | "ignore": [ 22 | "vscode" 23 | ] 24 | } 25 | ] 26 | }, 27 | "plugins": ["import", "@typescript-eslint/eslint-plugin"], 28 | "ignorePatterns": [ 29 | "**/*.d.ts" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | dist 3 | node_modules 4 | .vscode-test/ 5 | *.vsix 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run lint -------------------------------------------------------------------------------- /.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 | "dbaeumer.vscode-eslint" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.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 | "args": [ 13 | "--extensionDevelopmentPath=${workspaceFolder}" 14 | ], 15 | "outFiles": [ 16 | "${workspaceFolder}/out/**/*.js" 17 | ], 18 | "preLaunchTask": "${defaultBuildTask}" 19 | }, 20 | { 21 | "name": "Extension Tests", 22 | "type": "extensionHost", 23 | "request": "launch", 24 | "args": [ 25 | "--extensionDevelopmentPath=${workspaceFolder}", 26 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" 27 | ], 28 | "outFiles": [ 29 | "${workspaceFolder}/out/test/**/*.js" 30 | ], 31 | "preLaunchTask": "${defaultBuildTask}" 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /.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 | "cSpell.words": [ 12 | "kcounter" 13 | ], 14 | "C_Cpp.default.compilerPath": "/Users/batesgw1/.vscode/extensions/bartmanabyss.amiga-debug-1.0.0\\bin\\opt\\bin\\m68k-amiga-elf-gcc.exe" 15 | } -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/test/** 4 | src/** 5 | .gitignore 6 | .yarnrc 7 | vsc-extension-quickstart.md 8 | **/tsconfig.json 9 | **/.eslintrc.json 10 | **/*.map 11 | **/*.ts 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to the "68k-counter" 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 | ## [1.1.2] - 2021-07-05 10 | 11 | ### Fixed 12 | 13 | - Remove 'after' decorator which was affecting cursor position 14 | 15 | ## [1.1.1] - 2021-06-30 16 | 17 | ### Fixed 18 | 19 | - Debounce updates for improved performance 20 | - Handle single quoted strings 21 | 22 | ## [1.1.0] - 2021-06-30 23 | 24 | ### Added 25 | 26 | - Calculation tooltips 27 | - Macro and repeat processing 28 | - Improved variable and expression interpreting 29 | - Block byte counts 30 | 31 | ### Changed 32 | 33 | - Sizes now in bytes, not words 34 | 35 | ### Fixed 36 | 37 | - #1 Performance issue toggling timings 38 | - #2 Issues switching between editors 39 | - Inaccurate timings and missing instructions https://github.com/grahambates/68kcounter/issues/2 40 | 41 | ## [1.0.0] - 2021-04-11 42 | 43 | - Initial release 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2021 Eric Amodio 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 68k Counter for VS Code 2 | 3 | Displays cycle and size information for 68000 assembly source. 4 | 5 | ## Features 6 | 7 | ### Gutter annotations 8 | 9 | ![Output window screenshot](images/demo.gif) 10 | 11 | Provides annotations for each line of code to show: 12 | 13 | - Size in words 14 | - CPU cycles 15 | - Bus read cycles 16 | - Bus write cycles 17 | 18 | Click 'Toggle counts' at the top of a file to enable these. These counts live-update as you edit the code. 19 | 20 | ### Calculate totals 21 | 22 | Totals cycles and size across a range of lines. Either: 23 | 24 | - Select some text with count annotations enabled and the totals will be displayed in the status bar at the bottom of the screen. 25 | 26 | ![Selection counts](images/selection.png) 27 | 28 | - Call `68kcounter: Count selection` from the command palette with some text selected. 29 | 30 | ## Requirements 31 | 32 | An extension which provides a 68000 assembly language definition: 33 | 34 | - [Amiga Assemebly](https://marketplace.visualstudio.com/items?itemName=prb28.amiga-assembly) 35 | - [m68k](https://marketplace.visualstudio.com/items?itemName=steventattersall.m68k) 36 | - [Motorola 68k Assembly](https://marketplace.visualstudio.com/items?itemName=clcxce.motorola-68k-assembly) 37 | -------------------------------------------------------------------------------- /images/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grahambates/68kcounter-vscode/60cae261b48f1fd63567f5774ac4b22238c689c7/images/demo.gif -------------------------------------------------------------------------------- /images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grahambates/68kcounter-vscode/60cae261b48f1fd63567f5774ac4b22238c689c7/images/icon.png -------------------------------------------------------------------------------- /images/selection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grahambates/68kcounter-vscode/60cae261b48f1fd63567f5774ac4b22238c689c7/images/selection.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "68kcounter", 3 | "displayName": "68k Counter", 4 | "description": "68000 ASM source code cycle counter", 5 | "version": "1.2.0", 6 | "author": "Graham Bates ", 7 | "publisher": "gigabates", 8 | "license": "MIT", 9 | "icon": "images/icon.png", 10 | "homepage": "https://github.com/grahambates/68kcounter-vscode.git", 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/grahambates/68kcounter-vscode.git" 14 | }, 15 | "engines": { 16 | "vscode": "^1.55.0" 17 | }, 18 | "categories": [ 19 | "Debuggers", 20 | "Other" 21 | ], 22 | "activationEvents": [ 23 | "onLanguage:m68k", 24 | "onLanguage:vasmmot", 25 | "workspaceContains:**/*.{s,S,asm,ASM}" 26 | ], 27 | "main": "./out/extension.js", 28 | "contributes": { 29 | "commands": [ 30 | { 31 | "command": "68kcounter.toggleCounts", 32 | "title": "68kcounter: Toggle counts" 33 | }, 34 | { 35 | "command": "68kcounter.countSelection", 36 | "title": "68kcounter: Count selection" 37 | } 38 | ] 39 | }, 40 | "scripts": { 41 | "vscode:prepublish": "npm run compile", 42 | "compile": "tsc -p ./", 43 | "watch": "tsc -watch -p ./", 44 | "pretest": "npm run compile && npm run lint", 45 | "lint": "eslint . --ext .ts --ignore-path .gitignore", 46 | "test": "node ./out/test/runTest.js", 47 | "prepare": "husky install" 48 | }, 49 | "devDependencies": { 50 | "@types/debounce": "^1.2.0", 51 | "@types/glob": "^7.1.3", 52 | "@types/mocha": "^8.0.4", 53 | "@types/node": "^12.11.7", 54 | "@types/vscode": "^1.55.0", 55 | "@typescript-eslint/eslint-plugin": "^4.14.1", 56 | "@typescript-eslint/parser": "^4.14.1", 57 | "eslint": "^7.19.0", 58 | "eslint-config-prettier": "^8.1.0", 59 | "eslint-plugin-import": "^2.22.1", 60 | "eslint-plugin-prettier": "^3.3.1", 61 | "glob": "^7.1.6", 62 | "husky": "^6.0.0", 63 | "mocha": "^8.2.1", 64 | "prettier": "^2.3.2", 65 | "typescript": "^4.1.3", 66 | "vscode-test": "^1.5.0" 67 | }, 68 | "dependencies": { 69 | "68kcounter": "^3.1.2", 70 | "debounce": "^1.2.1" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/AnnotateCodeLensProvider.ts: -------------------------------------------------------------------------------- 1 | import { CodeLens, CodeLensProvider, Command, Range } from "vscode"; 2 | 3 | export default class AnnotateCodeLensProvider implements CodeLensProvider { 4 | async provideCodeLenses(): Promise { 5 | const topOfDocument = new Range(1, 0, 0, 0); 6 | 7 | const c: Command = { 8 | command: "68kcounter.toggleCounts", 9 | title: "Toggle counts", 10 | }; 11 | 12 | const codeLens = new CodeLens(topOfDocument, c); 13 | 14 | return [codeLens]; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/AnnotateController.ts: -------------------------------------------------------------------------------- 1 | import { Disposable, TextDocument, window, workspace } from "vscode"; 2 | import { Annotator } from "./Annotator"; 3 | 4 | /** 5 | * Manages Annotator instances across the workspace 6 | */ 7 | export default class AnnotateController implements Disposable { 8 | private documentMap: Map = new Map(); 9 | private subscription: Disposable; 10 | 11 | constructor() { 12 | this.subscription = workspace.onDidCloseTextDocument( 13 | this.onDidCloseTextDocument, 14 | this 15 | ); 16 | } 17 | 18 | toggle(): void { 19 | const document = window.activeTextEditor?.document; 20 | if (!document) { 21 | return; 22 | } 23 | const annotator = this.documentMap.get(document); 24 | if (annotator) { 25 | annotator.toggle(); 26 | } else { 27 | this.documentMap.set(document, new Annotator(document)); 28 | } 29 | } 30 | 31 | dispose(): void { 32 | this.documentMap.forEach((a) => a.dispose()); 33 | this.subscription.dispose(); 34 | } 35 | 36 | private onDidCloseTextDocument(doc: TextDocument) { 37 | const annotator = this.documentMap.get(doc); 38 | if (annotator) { 39 | this.documentMap.delete(doc); 40 | annotator.dispose(); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Annotator.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Disposable, 3 | Range, 4 | StatusBarAlignment, 5 | StatusBarItem, 6 | TextDocument, 7 | TextDocumentChangeEvent, 8 | TextEditor, 9 | TextEditorDecorationType, 10 | TextEditorSelectionChangeEvent, 11 | ThemeColor, 12 | window, 13 | workspace, 14 | } from "vscode"; 15 | import parse, { 16 | formatTiming, 17 | Level, 18 | Levels, 19 | timingLevel, 20 | calculateTotals, 21 | Line, 22 | Timing, 23 | } from "68kcounter"; 24 | import debounce from "debounce"; 25 | 26 | const colorPre = new ThemeColor("textPreformat.foreground"); 27 | const colors: Record = { 28 | [Levels.VHigh]: "#f44", 29 | [Levels.High]: new ThemeColor("editorError.foreground"), 30 | [Levels.Med]: new ThemeColor("editorWarning.foreground"), 31 | [Levels.Low]: new ThemeColor("editorInfo.foreground"), 32 | }; 33 | 34 | interface Annotation { 35 | text: string; 36 | hoverMessage: string; 37 | color: ThemeColor; 38 | } 39 | 40 | /** 41 | * Manages timing annotations for an editor instance 42 | */ 43 | export class Annotator implements Disposable { 44 | private document: TextDocument; 45 | private lines: Line[] = []; 46 | private statusBarItem: StatusBarItem; 47 | private disposable: Disposable; 48 | private visible = false; 49 | private type: TextEditorDecorationType; 50 | 51 | constructor(document: TextDocument) { 52 | this.type = window.createTextEditorDecorationType({ 53 | light: { 54 | before: { 55 | backgroundColor: "rgba(0, 0, 0, .02)", 56 | }, 57 | }, 58 | dark: { 59 | before: { 60 | backgroundColor: "rgba(255, 255, 255,.02)", 61 | }, 62 | }, 63 | before: { 64 | width: "210px", 65 | borderColor: new ThemeColor("editorInfo.foreground"), 66 | textDecoration: `;border-width: 0 2px 0 0; border-style: solid; margin-right: 26px; padding: 0 6px; text-align: right;`, 67 | }, 68 | }); 69 | 70 | this.document = document; 71 | const subscriptions: Disposable[] = []; 72 | workspace.onDidChangeTextDocument( 73 | debounce(this.onChange, 100, true), 74 | this, 75 | subscriptions 76 | ); 77 | window.onDidChangeTextEditorSelection( 78 | this.onSelection, 79 | this, 80 | subscriptions 81 | ); 82 | window.onDidChangeActiveTextEditor( 83 | this.onChangeEditor, 84 | this, 85 | subscriptions 86 | ); 87 | 88 | this.disposable = Disposable.from(...subscriptions); 89 | this.statusBarItem = window.createStatusBarItem(StatusBarAlignment.Left); 90 | this.show(); 91 | } 92 | 93 | dispose(): void { 94 | this.hide(); 95 | this.statusBarItem.dispose(); 96 | this.disposable.dispose(); 97 | this.type.dispose(); 98 | } 99 | 100 | toggle(): void { 101 | this.visible ? this.hide() : this.show(); 102 | } 103 | 104 | show(): void { 105 | this.visible = true; 106 | 107 | this.lines = parse(this.document.getText()); 108 | const annotations = this.lines.map(this.buildAnnotation); 109 | 110 | window.activeTextEditor?.setDecorations( 111 | this.type, 112 | annotations.map(({ text, color, hoverMessage }, i) => ({ 113 | range: new Range(i, 0, i, 0), 114 | hoverMessage, 115 | renderOptions: { before: { contentText: text || " ", color } }, 116 | })) 117 | ); 118 | this.showTotals(); 119 | } 120 | 121 | hide(): void { 122 | this.visible = false; 123 | window.activeTextEditor?.setDecorations(this.type, []); 124 | this.statusBarItem.hide(); 125 | } 126 | 127 | private showTotals() { 128 | const selection = window.activeTextEditor?.selection; 129 | const lines = 130 | selection && selection.start.line !== selection.end.line 131 | ? this.lines.slice(selection.start.line, selection.end.line + 1) 132 | : this.lines; 133 | 134 | const totals = calculateTotals(lines); 135 | let text = "Bytes: " + totals.bytes; 136 | if (totals.bssBytes) { 137 | text += ` (${totals.bssBytes} bss)`; 138 | } 139 | text += " | Cycles: " + formatTiming(totals.min); 140 | if (totals.isRange) { 141 | text += "–" + formatTiming(totals.max); 142 | } 143 | this.statusBarItem.text = text; 144 | this.statusBarItem.show(); 145 | } 146 | 147 | /** 148 | * Get annotation text and color for a line of code 149 | */ 150 | private buildAnnotation(line: Line): Annotation { 151 | const { bytes, timing } = line; 152 | let text = ""; 153 | let color = colorPre; 154 | if (timing) { 155 | text += timing.values.map(formatTiming).join(" "); 156 | const level = timingLevel(timing.values[0]); 157 | color = colors[level]; 158 | } 159 | if (bytes) { 160 | text += " " + bytes; 161 | } 162 | 163 | const infoLines: string[] = []; 164 | if (line.timing && line.timing.values.length > 1) { 165 | infoLines.push(line.timing.labels.join(" / ")); 166 | } 167 | const calculation = line.timing?.calculation; 168 | if (calculation) { 169 | if ( 170 | calculation.multiplier || 171 | (calculation?.ea && calculation.ea[0] > 0) 172 | ) { 173 | let calc = formatCalculation( 174 | calculation.base[0], 175 | calculation.multiplier 176 | ); 177 | if (calculation?.ea && calculation.ea[0] > 0) { 178 | calc += ` + EA: ${formatTiming(calculation.ea)}`; 179 | } 180 | infoLines.push(calc); 181 | } 182 | if (calculation?.n) { 183 | infoLines.push(`n = ${calculation.n}`); 184 | } 185 | } 186 | 187 | const hoverMessage = 188 | infoLines.length > 1 189 | ? infoLines.map((s) => " * " + s).join("\n") 190 | : infoLines.join("\n"); 191 | 192 | return { text, color, hoverMessage }; 193 | } 194 | 195 | private onSelection(e: TextEditorSelectionChangeEvent) { 196 | if (this.visible && e.textEditor.document === this.document) { 197 | this.showTotals(); 198 | } 199 | } 200 | 201 | private onChangeEditor(e?: TextEditor) { 202 | if (this.visible && e?.document === this.document) { 203 | this.show(); 204 | } else { 205 | this.statusBarItem.hide(); 206 | } 207 | } 208 | 209 | private onChange(e: TextDocumentChangeEvent) { 210 | if (this.visible && e.document === this.document) { 211 | this.show(); 212 | } 213 | } 214 | } 215 | 216 | const formatCalculation = (timing: Timing, multiplier?: Timing) => { 217 | const strVals = timing.map((v) => String(v)); 218 | 219 | if (multiplier) { 220 | for (const i in multiplier) { 221 | if (multiplier[i]) { 222 | strVals[i] += "+" + multiplier[i] + "n"; 223 | } 224 | } 225 | } 226 | 227 | return `${strVals[0]}(${strVals[1]}/${strVals[2]})`; 228 | }; 229 | -------------------------------------------------------------------------------- /src/countSelection.ts: -------------------------------------------------------------------------------- 1 | import { Range, window } from "vscode"; 2 | import process, { calculateTotals, formatTiming } from "68kcounter"; 3 | 4 | export default function countSelection(): void { 5 | const editor = window.activeTextEditor; 6 | if (!editor) { 7 | return; 8 | } 9 | const selection = editor.selection; 10 | const source = editor.document.getText( 11 | new Range(selection.start.line, 0, selection.end.line + 1, 0) 12 | ); 13 | const selectedLines = process(source); 14 | 15 | const totals = calculateTotals(selectedLines); 16 | let text = "Bytes: " + totals.bytes; 17 | if (totals.bssBytes) { 18 | text += ` (${totals.bssBytes} bss)`; 19 | } 20 | text += " Cycles: " + formatTiming(totals.min); 21 | if (totals.isRange) { 22 | text += "–" + formatTiming(totals.max); 23 | } 24 | window.showInformationMessage(text); 25 | } 26 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import { commands, ExtensionContext, languages } from "vscode"; 2 | import AnnotateCodeLensProvider from "./AnnotateCodeLensProvider"; 3 | import AnnotateController from "./AnnotateController"; 4 | import countSelection from "./countSelection"; 5 | 6 | export function activate(context: ExtensionContext): void { 7 | const controller = new AnnotateController(); 8 | context.subscriptions.push(controller); 9 | 10 | context.subscriptions.push( 11 | commands.registerCommand("68kcounter.toggleCounts", () => 12 | controller.toggle() 13 | ) 14 | ); 15 | 16 | context.subscriptions.push( 17 | commands.registerCommand("68kcounter.countSelection", () => 18 | countSelection() 19 | ) 20 | ); 21 | 22 | const codeLensProvider = new AnnotateCodeLensProvider(); 23 | 24 | context.subscriptions.push( 25 | languages.registerCodeLensProvider( 26 | { pattern: "**/*.{s,i,asm}" }, 27 | codeLensProvider 28 | ) 29 | ); 30 | } 31 | 32 | export function deactivate(): void { 33 | // 34 | } 35 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/test/suite/extension.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert"; 2 | 3 | // You can import and use all API from the 'vscode' module 4 | // as well as import your extension to test it 5 | import * as vscode from "vscode"; 6 | // import * as myExtension from '../../extension'; 7 | 8 | suite("Extension Test Suite", () => { 9 | vscode.window.showInformationMessage("Start all tests."); 10 | 11 | test("Sample test", () => { 12 | assert.strictEqual(-1, [1, 2, 3].indexOf(5)); 13 | assert.strictEqual(-1, [1, 2, 3].indexOf(0)); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/test/suite/index.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import Mocha from "mocha"; 3 | import 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 | console.error(err); 34 | e(err); 35 | } 36 | }); 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "lib": [ 7 | "es6" 8 | ], 9 | "strict": true, 10 | "noImplicitAny": true, 11 | "esModuleInterop": true, 12 | "skipLibCheck": true, 13 | "forceConsistentCasingInFileNames": true, 14 | }, 15 | "exclude": [ 16 | "node_modules", 17 | ".vscode-test" 18 | ] 19 | } 20 | --------------------------------------------------------------------------------