diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..ad37dd8 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,4 @@ +- test backed code changes +- new code matches existing style (enforced via tslint) +- bug fixes always welcome :) +- new feature proposals go through a github issue \ No newline at end of file diff --git a/README.md b/README.md index 2ac5210..e7151c8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,14 @@ # coverage-gutters +[![Version](https://vsmarketplacebadge.apphb.com/version/ryanluker.vscode-coverage-gutters.svg)](https://marketplace.visualstudio.com/items?itemName=ryanluker.vscode-coverage-gutters) +[![Installs](https://vsmarketplacebadge.apphb.com/installs/ryanluker.vscode-coverage-gutters.svg)](https://marketplace.visualstudio.com/items?itemName=ryanluker.vscode-coverage-gutters) +[![Ratings](https://vsmarketplacebadge.apphb.com/rating/ryanluker.vscode-coverage-gutters.svg)](https://marketplace.visualstudio.com/items?itemName=ryanluker.vscode-coverage-gutters) + [![Build Status](https://travis-ci.org/ryanluker/vscode-coverage-gutters.svg?branch=master)](https://travis-ci.org/ryanluker/vscode-coverage-gutters) [![Build status](https://ci.appveyor.com/api/projects/status/8vb8t787frcqtrm7?svg=true)](https://ci.appveyor.com/project/ryanluker/vscode-coverage-gutters) + +[![Average time to resolve an issue](https://isitmaintained.com/badge/resolution/ryanluker/vscode-coverage-gutters.svg)](https://isitmaintained.com/project/ryanluker/vscode-coverage-gutters "Average time to resolve an issue") +[![Percentage of issues still open](https://isitmaintained.com/badge/open/ryanluker/vscode-coverage-gutters.svg)](https://isitmaintained.com/project/ryanluker/vscode-coverage-gutters "Percentage of issues still open") + [![Buy me a coffee](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=XRWWCAZBYB9SG&lc=CA&item_name=vscode%2dcoverage%2dgutters¤cy_code=CAD&bn=PP%2dDonationsBF%3abtn_donate_SM%2egif%3aNonHostedGuest) ## Features @@ -12,12 +20,17 @@ ![Coverage Gutters features options](images/coverage-gutters-features-2.PNG) +- preview overall coverage inside vscode + +![Coverage Gutters preview overall coverage](images/preview-lcov-1.PNG) + +- multi lcov info support via option picker - relative lcov file resolution option for those with complex file paths - workspace settings to customize the features to your liking - colour compatibility with light and dark themes ## Requirements -- vscode 1.11.x and up +- vscode 1.14.x and up - macos, linux or windows ## Extension Settings @@ -42,10 +55,12 @@ |`coverage-gutters.showRulerCoverage`|Show or hide the ruler coverage |`coverage-gutters.showGutterCoverage`|Show or hide the gutter coverage |`coverage-gutters.customizable.status-bar-toggler-watchLcovAndVisibleEditors-enabled`|Setting this to false will remove the status bar item +|`coverage-gutters.customizable.menus-editor-context-previewLcovReport-enabled`|Setting this to false will remove the command from the editor context menu and vice versa |`coverage-gutters.customizable.menus-editor-context-displayCoverage-enabled`|Setting this to false will remove the command from the editor context menu and vice versa |`coverage-gutters.customizable.menus-editor-context-watchLcovFile-enabled`|Setting this to false will remove the command from the editor context menu and vice versa |`coverage-gutters.customizable.menus-editor-context-watchVisibleEditors-enabled`|Setting this to false will remove the command from the editor context menu and vice versa |`coverage-gutters.customizable.menus-editor-context-removeCoverage-enabled`|Setting this to false will remove the command from the editor context menu and vice versa +|`coverage-gutters.customizable.keybindings-previewLcovReport-enabled`|Setting this to false will remove the shortcut and vice versa |`coverage-gutters.customizable.keybindings-displayCoverage-enabled`|Setting this to false will remove the shortcut and vice versa |`coverage-gutters.customizable.keybindings-watchLcovFile-enabled`|Setting this to false will remove the shortcut and vice versa |`coverage-gutters.customizable.keybindings-watchVisibleEditors-enabled`|Setting this to false will remove the shortcut and vice versa @@ -66,10 +81,7 @@ Some examples for the highlight colour are as follows: ### [Changelog](https://github.com/ryanluker/vscode-coverage-gutters/releases) ## Contribution Guidelines -- test backed code changes -- new code matches existing style (enforced via tslint) -- bug fixes always welcome :) -- new feature proposals go through a github issue +### [Guidelines](/CONTRIBUTING.md) ----------------------------------------------------------------------------------------------------------- diff --git a/example/package.json b/example/package.json index c84748b..41cd3e3 100644 --- a/example/package.json +++ b/example/package.json @@ -4,7 +4,7 @@ "description": "", "main": "test-coverage.js", "scripts": { - "test": "istanbul cover -x \"./test-coverage.js\" --report lcovonly ./node_modules/mocha/bin/_mocha -- -R spec test.js" + "test": "istanbul cover -x \"./test-coverage.js\" ./node_modules/mocha/bin/_mocha -- -R spec test.js" }, "author": "", "license": "ISC", diff --git a/images/preview-lcov-1.PNG b/images/preview-lcov-1.PNG new file mode 100644 index 0000000..bc4dfe7 Binary files /dev/null and b/images/preview-lcov-1.PNG differ diff --git a/package.json b/package.json index 0f1a874..68ad0f7 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "vscode-coverage-gutters", "displayName": "Coverage Gutters", "description": "Display test coverage generated by lcov - works with many languages", - "version": "1.0.0", + "version": "1.1.0", "license": "MIT", "repository": { "type": "git", @@ -123,6 +123,11 @@ "default": true, "description": "enable or disable the status bar item" }, + "coverage-gutters.customizable.menus-editor-context-previewLcovReport-enabled": { + "type": "boolean", + "default": true, + "description": "enable or disable the previewLcovReport command in the editor/context menu" + }, "coverage-gutters.customizable.menus-editor-context-displayCoverage-enabled": { "type": "boolean", "default": true, @@ -143,6 +148,11 @@ "default": true, "description": "enable or disable the removeCoverage command in the editor/context menu" }, + "coverage-gutters.customizable.keybindings-previewLcovReport-enabled": { + "type": "boolean", + "default": true, + "description": "enable or disable the keybinding shortcut for previewing the lcov report" + }, "coverage-gutters.customizable.keybindings-displayCoverage-enabled": { "type": "boolean", "default": true, @@ -166,6 +176,10 @@ } }, "commands": [ + { + "command": "extension.previewLcovReport", + "title": "Coverage Gutters: Preview Lcov Report" + }, { "command": "extension.displayCoverage", "title": "Coverage Gutters: Display File Coverage" @@ -184,6 +198,12 @@ } ], "keybindings": [ + { + "command": "extension.previewLcovReport", + "key": "ctrl+shift+6", + "mac": "shift+cmd+6", + "when": "config.coverage-gutters.customizable.keybindings-previewLcovReport-enabled" + }, { "command": "extension.displayCoverage", "key": "ctrl+shift+7", @@ -211,25 +231,30 @@ ], "menus": { "editor/context": [ + { + "when": "config.coverage-gutters.customizable.menus-editor-context-previewLcovReport-enabled", + "command": "extension.previewLcovReport", + "group": "Coverage-Gutters@1" + }, { "when": "config.coverage-gutters.customizable.menus-editor-context-displayCoverage-enabled", "command": "extension.displayCoverage", - "group": "Coverage-Gutters@1" + "group": "Coverage-Gutters@2" }, { "when": "config.coverage-gutters.customizable.menus-editor-context-watchLcovAndVisibleEditors-enabled", "command": "extension.watchLcovAndVisibleEditors", - "group": "Coverage-Gutters@2" + "group": "Coverage-Gutters@3" }, { "when": "config.coverage-gutters.customizable.menus-editor-context-removeWatch-enabled", "command": "extension.removeWatch", - "group": "Coverage-Gutters@3" + "group": "Coverage-Gutters@4" }, { "when": "config.coverage-gutters.customizable.menus-editor-context-removeCoverage-enabled", "command": "extension.removeCoverage", - "group": "Coverage-Gutters@4" + "group": "Coverage-Gutters@5" } ] } @@ -251,7 +276,6 @@ "@types/mocha": "2.2.41", "@types/node": "6.0.40", "@types/request": "0.0.43", - "@types/uuid": "3.0.0", "mocha": "2.3.3", "tslint": "5.4.3", "typescript": "2.3.4", @@ -260,7 +284,6 @@ "dependencies": { "glob": "5.0.15", "lcov-parse": "0.0.10", - "request": "2.81.0", - "uuid": "3.0.1" + "request": "2.81.0" } } diff --git a/src/extension.ts b/src/extension.ts index 11e339e..3611a1b 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -23,7 +23,17 @@ export function activate(context: vscode.ExtensionContext) { const statusBarToggler = new StatusBarToggler(configStore); const lcov = new Lcov(configStore, globImpl, vscodeImpl, fsImpl); const indicators = new Indicators(parseImpl, vscodeImpl, configStore); - const gutters = new Gutters(configStore, lcov, indicators, reporter, statusBarToggler); + const gutters = new Gutters( + configStore, + lcov, + indicators, + reporter, + statusBarToggler, + ); + + const previewLcovReport = vscode.commands.registerCommand("extension.previewLcovReport", () => { + gutters.previewLcovReport(); + }); const display = vscode.commands.registerCommand("extension.displayCoverage", () => { gutters.displayCoverageForActiveFile(); @@ -41,6 +51,7 @@ export function activate(context: vscode.ExtensionContext) { gutters.removeCoverageForActiveFile(); }); + context.subscriptions.push(previewLcovReport); context.subscriptions.push(remove); context.subscriptions.push(display); context.subscriptions.push(watch); diff --git a/src/gutters.ts b/src/gutters.ts index a91d908..62ec756 100644 --- a/src/gutters.ts +++ b/src/gutters.ts @@ -1,9 +1,12 @@ import { + commands, Disposable, FileSystemWatcher, StatusBarItem, TextEditor, + Uri, version, + ViewColumn, window, } from "vscode"; @@ -44,13 +47,52 @@ export class Gutters { this.reporter.sendEvent("user", "vscodeVersion", version); } + public async previewLcovReport() { + try { + const lcovReports = await this.lcov.findReports(); + let pickedReport: string; + if (lcovReports.length === 1) { + pickedReport = lcovReports[0]; + } else { + this.reporter.sendEvent("user", "showQuickPickReport", `${lcovReports.length}`); + pickedReport = await window.showQuickPick( + lcovReports, + {placeHolder: "Choose a Lcov Report to preview."}, + ); + } + + if (!pickedReport) { throw new Error("Could not show Lcov Report file!"); } + const reportUri = Uri.parse(`file:///${pickedReport}`); + await commands.executeCommand( + "vscode.previewHtml", + reportUri, + ViewColumn.One, + "Preview Lcov Report", + ); + this.reporter.sendEvent("user", "preview-lcov-report"); + } catch (error) { + this.handleError(error); + } + } + public async displayCoverageForActiveFile() { const textEditor = window.activeTextEditor; try { if (!textEditor) { return; } - const lcovPath = await this.lcov.find(); - await this.loadAndRenderCoverage(textEditor, lcovPath); - + const lcovPaths = await this.lcov.findLcovs(); + let pickedLcov: string; + if (lcovPaths.length === 1) { + pickedLcov = lcovPaths[0]; + } else { + this.reporter.sendEvent("user", "showQuickPickLcov", `${lcovPaths.length}`); + pickedLcov = await window.showQuickPick( + lcovPaths, + {placeHolder: "Choose a Lcov File to use for coverage."}, + ); + } + if (!pickedLcov) { throw new Error("Could not show coverage for file!"); } + + await this.loadAndRenderCoverage(textEditor, pickedLcov); this.reporter.sendEvent("user", "display-coverage"); } catch (error) { this.handleError(error); @@ -62,16 +104,28 @@ export class Gutters { const textEditor = window.activeTextEditor; try { - const lcovPath = await this.lcov.find(); + const lcovPaths = await this.lcov.findLcovs(); + + let pickedLcov: string; + if (lcovPaths.length === 1) { + pickedLcov = lcovPaths[0]; + } else { + this.reporter.sendEvent("user", "showQuickPick", `${lcovPaths.length}`); + pickedLcov = await window.showQuickPick( + lcovPaths, + {placeHolder: "Choose a Lcov to use for coverage."}, + ); + } + if (!pickedLcov) { throw new Error("Could not show coverage for file!"); } // When we try to load the coverage when watch is actived we dont want to error // if the active file has no coverage - this.loadAndRenderCoverage(textEditor, lcovPath).catch(() => {}); + this.loadAndRenderCoverage(textEditor, pickedLcov).catch(() => {}); - this.lcovWatcher = vscodeImpl.watchFile(lcovPath); - this.lcovWatcher.onDidChange((event) => this.renderCoverageOnVisible(lcovPath)); + this.lcovWatcher = vscodeImpl.watchFile(pickedLcov); + this.lcovWatcher.onDidChange((event) => this.renderCoverageOnVisible(pickedLcov)); this.editorWatcher = window.onDidChangeVisibleTextEditors( - (event) => this.renderCoverageOnVisible(lcovPath)); + (event) => this.renderCoverageOnVisible(pickedLcov)); this.statusBar.toggle(); this.reporter.sendEvent("user", "watch-lcov-editors"); @@ -115,7 +169,6 @@ export class Gutters { private handleError(error: Error) { const message = error.message ? error.message : error; window.showWarningMessage(message.toString()); - this.reporter.sendEvent("error", message.toString()); } diff --git a/src/lcov.ts b/src/lcov.ts index 28479d3..2d7ed77 100644 --- a/src/lcov.ts +++ b/src/lcov.ts @@ -21,15 +21,26 @@ export class Lcov { this.fs = fs; } - public find(): Promise { - return new Promise((resolve, reject) => { + public findLcovs(): Promise { + return new Promise((resolve, reject) => { this.glob.find( `**/${this.configStore.lcovFileName}`, { ignore: "**/node_modules/**", cwd: this.vscode.getRootPath(), realpath: true }, (err, files) => { - if (!files || !files.length) { return reject("Could not find a lcov file!"); } - if (files.length > 1) { return reject("More then one lcov file found!"); } - return resolve(files[0]); + if (!files || !files.length) { return reject("Could not find a Lcov File!"); } + return resolve(files); + }); + }); + } + + public findReports(): Promise { + return new Promise((resolve, reject) => { + this.glob.find( + `**/coverage/**/*.html`, + { ignore: "**/node_modules/**", cwd: this.vscode.getRootPath(), realpath: true }, + (err, files) => { + if (!files || !files.length) { return reject("Could not find a Lcov Report file!"); } + return resolve(files); }); }); } diff --git a/test/lcov.test.ts b/test/lcov.test.ts index d62d438..0ace00a 100644 --- a/test/lcov.test.ts +++ b/test/lcov.test.ts @@ -41,7 +41,7 @@ suite("Lcov Tests", function() { } }); - test("#find: Should return error if more then one file found for lcovFileName", function(done) { + test("#find: Should not return error if more then one file found for lcovFileName", function(done) { const globImpl = new Glob(); const vscodeImpl = new Vscode(); const fsImpl = new Fs(); @@ -58,14 +58,13 @@ suite("Lcov Tests", function() { fsImpl, ); - lcov.find() - .then(function() { - return done(new Error("Expected error did not fire!")); + lcov.findLcovs() + .then(function(files) { + assert.equal(files.length, 2); + return done(); }) .catch(function(error) { - if (error.name === "AssertionError") { return done(error); } - if (error === "More then one lcov file found!") { return done(); } - return done(error); + return done(new Error("unexpected error did fire!")); }); }); @@ -86,13 +85,13 @@ suite("Lcov Tests", function() { fsImpl, ); - lcov.find() + lcov.findLcovs() .then(function() { return done(new Error("Expected error did not fire!")); }) .catch(function(error) { if (error.name === "AssertionError") { return done(error); } - if (error === "Could not find a lcov file!") { return done(); } + if (error === "Could not find a Lcov File!") { return done(); } return done(error); }); }); @@ -114,9 +113,9 @@ suite("Lcov Tests", function() { fsImpl, ); - lcov.find() - .then(function(fsPath) { - assert.equal(fsPath, "path/to/greatness/test.ts"); + lcov.findLcovs() + .then(function(fsPaths) { + assert.equal(fsPaths[0], "path/to/greatness/test.ts"); return done(); }) .catch(function(error) {