Skip to content

Commit

Permalink
Add true gutters and increase testability (#28)
Browse files Browse the repository at this point in the history
* add changelog and update readme

* increment version

* update parse lcov d.ts

* update md's

* wrap external modules

* refactor for testability

* cleanup tests

* allow linux and windows to pass tests

* flesh out gutters tests

* cleanup types on config

* remove dispose test from unit

* setup test files

* remove dones

* cover lcov with tests

* fix tests to work with CI

* add gutter icons and customization

* update feature gifs and readme
  • Loading branch information
ryanluker committed Mar 20, 2017
1 parent 86528da commit 8ba8199
Show file tree
Hide file tree
Showing 22 changed files with 525 additions and 121 deletions.
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
### 0.2.0
- true gutter indicators
- contribution doc
- refactor gutters to be more testable

### 0.1.3
- colour updates
- context menu additions
- icon resizing

### 0.1.2
- minor doc tweaks
- cleanup default colours and use rgba

### 0.1.1
- give the icon a background

### 0.1.0
- display and remove lcov line coverage using commands
- modify highlight colour using workspace settings
- modify lcov name using workspace settings
21 changes: 14 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@

## Features
- simple line coverage rendering using lcov
- workspace settings to change the lcov name and highlight colours

![Coverage Gutters features context](images/coverage-gutters-features-context.gif)

- workspace settings to customize the features to your liking
- colour compatibility with light and dark themes

![Coverage Gutters features reel](images/coverage-gutters-features.gif)
![Coverage Gutters features basic](images/coverage-gutters-features-basic.gif)

## Requirements
- vscode 1.5.0 and up
- vscode 1.9.0 and up
- macos, linux or windows

## Extension Settings
Expand All @@ -20,6 +23,8 @@
|`coverage-gutters.lcovname`|Allows specification of a custom lcov file name
|`coverage-gutters.highlightlight`|Changes the highlight for light themes
|`coverage-gutters.highlightdark`|Changes the Highlight for dark themes
|`coverage-gutters.gutterIconPathDark`|Relative path to an icon in the extension for dark themes
|`coverage-gutters.gutterIconPathLight`|Relative path to an icon in the extension for light themes
|`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-removeCoverage-enabled`|Setting this to false will remove the command from the editor context menu and vice versa

Expand All @@ -35,11 +40,13 @@ Some examples for the highlight colour are as follows:
- none ( just missing functionality :) )

## Release Notes
### [Changelog](CHANGELOG.mb)

### 0.1.0
- display and remove lcov line coverage using commands
- modify highlight colour using workspace settings
- modify lcov name using workspace settings
## Contribution Guidelines
- test backed code changes
- new code matches existing style
- bug fixes always welcome :)
- new feature proposals go through a github issue

-----------------------------------------------------------------------------------------------------------

Expand Down
Binary file added images/coverage-gutters-features-basic.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/coverage-gutters-features-context.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed images/coverage-gutters-features.gif
Binary file not shown.
1 change: 1 addition & 0 deletions images/gutter-icon-dark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions images/gutter-icon-light.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 11 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "vscode-coverage-gutters",
"displayName": "Coverage Gutters",
"description": "Display test coverage generated by lcov - works with many languages",
"version": "0.1.3",
"version": "0.2.0",
"license": "MIT",
"repository": {
"type": "git",
Expand Down Expand Up @@ -48,6 +48,16 @@
"default": "rgba(36, 56, 27, 0.75)",
"description": "dark themed highlight for code coverage"
},
"coverage-gutters.gutterIconPathDark": {
"type": "string",
"default": "./images/gutter-icon-dark.svg",
"description": "path to an icon (svg, png, etc) for displaying in the gutter for dark themes"
},
"coverage-gutters.gutterIconPathLight": {
"type": "string",
"default": "./images/gutter-icon-light.svg",
"description": "path to an icon (svg, png, etc) for displaying in the gutter for light themes"
},
"coverage-gutters.customizable.menus-editor-context-displayCoverage-enabled": {
"type": "boolean",
"default": true,
Expand Down
81 changes: 81 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
'use strict';

import {TextEditorDecorationType, ExtensionContext} from "vscode";

export interface configStore {
lcovFileName: string,
coverageDecorationType: TextEditorDecorationType,
gutterDecorationType: TextEditorDecorationType
}

export class Config {
private createTextEditorDecorationType;
private executeCommand;
private workspaceConfig;
private context: ExtensionContext;

private lcovFileName: string;
private coverageDecorationType: TextEditorDecorationType;
private gutterDecorationType: TextEditorDecorationType;

constructor(
createTextEditorDecorationType,
executeCommand,
workspaceConfig,
context: ExtensionContext
) {
this.createTextEditorDecorationType = createTextEditorDecorationType;
this.executeCommand = executeCommand;
this.workspaceConfig = workspaceConfig;
this.context = context;
}

public get(): configStore {
return {
lcovFileName: this.lcovFileName,
coverageDecorationType: this.coverageDecorationType,
gutterDecorationType: this.gutterDecorationType
}
}

public setup(): configStore {
//Customizable UI configurations
const rootCustomConfig = this.workspaceConfig("coverage-gutters.customizable");
const configsCustom = Object.keys(rootCustomConfig);
for(let element of configsCustom) {
this.executeCommand(
"setContext",
"config.coverage-gutters.customizable." + element,
rootCustomConfig.get(element));
}

//Basic configurations
const rootConfig = this.workspaceConfig("coverage-gutters");
this.lcovFileName = rootConfig.get("lcovname") as string;
const coverageLightBackgroundColour = rootConfig.get("highlightlight") as string;
const coverageDarkBackgroundColour = rootConfig.get("highlightdark") as string;
const gutterIconPathDark = rootConfig.get("gutterIconPathDark") as string;
const gutterIconPathLight = rootConfig.get("gutterIconPathLight") as string;

this.coverageDecorationType = this.createTextEditorDecorationType({
isWholeLine: true,
light: {
backgroundColor: coverageLightBackgroundColour
},
dark: {
backgroundColor: coverageDarkBackgroundColour
}
});

this.gutterDecorationType = this.createTextEditorDecorationType({
light: {
gutterIconPath: this.context.asAbsolutePath(gutterIconPathLight)
},
dark: {
gutterIconPath: this.context.asAbsolutePath(gutterIconPathDark)
}
});

return this.get();
}
}
2 changes: 1 addition & 1 deletion src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as vscode from "vscode";
import {Gutters} from "./gutters";

export function activate(context: vscode.ExtensionContext) {
let gutters = new Gutters();
let gutters = new Gutters(context);

let display = vscode.commands.registerCommand("extension.displayCoverage", () => {
gutters.displayCoverageForActiveFile();
Expand Down
121 changes: 34 additions & 87 deletions src/gutters.ts
Original file line number Diff line number Diff line change
@@ -1,103 +1,50 @@
'use strict';

import * as vscode from "vscode";
import * as parse from "lcov-parse";
import {readFile} from "fs";
import {
createTextEditorDecorationType,
executeCommand,
getConfiguration,
setDecorations,
findFiles
} from "./wrappers/vscode";
import {readFile} from "./wrappers/fs";
import {lcovParse} from "./wrappers/lcov-parse";
import {Range, window, ExtensionContext} from "vscode";

import {Lcov, lcov} from "./lcov";
import {Indicators, indicators} from "./indicators";
import {Config, configStore} from "./config";

export class Gutters {
private lcovFileName: string;
private coverageDecorationType: vscode.TextEditorDecorationType;
private coverageLightBackgroundColour: string;
private coverageDarkBackgroundColour: string;

constructor() {
const config = vscode.workspace.getConfiguration("coverage-gutters");

//Customizable UI configurations
const configsCustom = Object.keys(config.get("customizable"));
for(let element of configsCustom) {
vscode.commands.executeCommand(
"setContext",
"config.coverage-gutters.customizable." + element,
vscode.workspace.getConfiguration("coverage-gutters.customizable").get(element));
}

//Basic configurations
this.lcovFileName = config.get("lcovname") as string;
this.coverageLightBackgroundColour = config.get("highlightlight") as string;
this.coverageDarkBackgroundColour = config.get("highlightdark") as string;

this.coverageDecorationType = vscode.window.createTextEditorDecorationType({
isWholeLine: true,
light: {
backgroundColor: this.coverageLightBackgroundColour
},
dark: {
backgroundColor: this.coverageDarkBackgroundColour
}
});
private configStore: configStore;
private lcov: lcov;
private indicators: indicators;

constructor(context: ExtensionContext) {
this.configStore = new Config(
createTextEditorDecorationType,
executeCommand,
getConfiguration,
context
).setup();
this.lcov = new Lcov(this.configStore, findFiles, readFile);
this.indicators = new Indicators(this.configStore, lcovParse, setDecorations);
}

public async displayCoverageForActiveFile() {
try {
const activeFile = vscode.window.activeTextEditor.document.fileName;
const lcovPath = await this.findLcov();
const lcovFile = await this.loadLcov(lcovPath);
const coveredLines = await this.extractCoverage(lcovFile, activeFile);
await this.renderIndicators(coveredLines);
const activeFile = window.activeTextEditor.document.fileName;
const lcovPath = await this.lcov.find();
const lcovFile = await this.lcov.load(lcovPath);
const coveredLines = await this.indicators.extract(lcovFile, activeFile);
await this.indicators.render(coveredLines);
} catch(e) {
console.log(e);
}
}

public dispose() {
vscode.window.activeTextEditor.setDecorations(this.coverageDecorationType, []);
}

private findLcov(): Promise<string> {
return new Promise((resolve, reject) => {
vscode.workspace.findFiles("**/" + this.lcovFileName, "**/node_modules/**", 1)
.then((uriLcov) => {
if(!uriLcov.length) return reject(new Error("Could not find a lcov file!"));
return resolve(uriLcov[0].fsPath);
});
});
}

private renderIndicators(lines: Detail[]): Promise<Function> {
return new Promise((resolve, reject) => {
let renderLines = [];
lines.forEach((detail) => {
if(detail.hit > 0) {
renderLines.push(new vscode.Range(detail.line - 1, 0, detail.line - 1, 0));
}
});
vscode.window.activeTextEditor.setDecorations(this.coverageDecorationType, renderLines);
return resolve();
});
}

private loadLcov(lcovPath: string): Promise<string> {
return new Promise<string>((resolve, reject) => {
readFile(lcovPath, (err, data) => {
if(err) return reject(err);
return resolve(data.toString());
});
});
}

private extractCoverage(lcovFile: string, file: string): Promise<Array<Detail>> {
return new Promise<Array<Detail>>((resolve, reject) => {
parse(lcovFile, (err, data) => {
if(err) return reject(err);
let section = data.find((section) => {
//prevent hazardous casing mishaps
return section.file.toLocaleLowerCase() === file.toLocaleLowerCase();
});

if(!section) return reject(new Error("No coverage for file!"));
return resolve(section.lines.details);
});
});
setDecorations(this.configStore.coverageDecorationType, []);
setDecorations(this.configStore.gutterDecorationType, []);
}
}
51 changes: 51 additions & 0 deletions src/indicators.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"use strict";

import {configStore} from "./config";
import {Range} from "vscode";
import {Detail} from "lcov-parse";

export interface indicators {
render(lines: Array<Detail>): Promise<string>,
extract(lcovFile: string, file: string): Promise<Array<Detail>>
}

export class Indicators implements indicators{
private parseLcov;
private configStore: configStore;
private setDecorations;

constructor(configStore, parseLcov, setDecorations) {
this.configStore = configStore;
this.parseLcov = parseLcov;
this.setDecorations = setDecorations;
}

public render(lines: Detail[]): Promise<string> {
return new Promise<string>((resolve, reject) => {
let renderLines = [];
lines.forEach((detail) => {
if(detail.hit > 0) {
renderLines.push(new Range(detail.line - 1, 0, detail.line - 1, 0));
}
});
this.setDecorations(this.configStore.coverageDecorationType, renderLines);
this.setDecorations(this.configStore.gutterDecorationType, renderLines);
return resolve();
});
}

public extract(lcovFile: string, file: string): Promise<Array<Detail>> {
return new Promise<Array<Detail>>((resolve, reject) => {
this.parseLcov(lcovFile, (err, data) => {
if(err) return reject(err);
let section = data.find((section) => {
//prevent hazardous casing mishaps
return section.file.toLocaleLowerCase() === file.toLocaleLowerCase();
});

if(!section) return reject(new Error("No coverage for file!"));
return resolve(section.lines.details);
});
});
}
}
Loading

0 comments on commit 8ba8199

Please sign in to comment.