Merge pull request #202048 from microsoft/connor4312/test-coverage-decorations-1
testing: add initial editor decorationspull/202106/head
commit
801d79e284
|
@ -679,6 +679,8 @@
|
|||
"--vscode-terminalOverviewRuler-findMatchForeground",
|
||||
"--vscode-terminalStickyScroll-background",
|
||||
"--vscode-terminalStickyScrollHover-background",
|
||||
"--vscode-testing-coveredBackground",
|
||||
"--vscode-testing-coveredGutterBackground",
|
||||
"--vscode-testing-iconErrored",
|
||||
"--vscode-testing-iconFailed",
|
||||
"--vscode-testing-iconPassed",
|
||||
|
@ -692,6 +694,8 @@
|
|||
"--vscode-testing-peekBorder",
|
||||
"--vscode-testing-peekHeaderBackground",
|
||||
"--vscode-testing-runAction",
|
||||
"--vscode-testing-uncoveredBackground",
|
||||
"--vscode-testing-uncoveredGutterBackground",
|
||||
"--vscode-textBlockQuote-background",
|
||||
"--vscode-textBlockQuote-border",
|
||||
"--vscode-textCodeBlock-background",
|
||||
|
|
|
@ -250,7 +250,7 @@ export class View extends ViewEventHandler {
|
|||
|
||||
// Add all margin decorations
|
||||
glyphs = glyphs.concat(model.getAllMarginDecorations().map((decoration) => {
|
||||
const lane = decoration.options.glyphMargin?.position ?? GlyphMarginLane.Left;
|
||||
const lane = decoration.options.glyphMargin?.position ?? GlyphMarginLane.Center;
|
||||
return { range: decoration.range, lane };
|
||||
}));
|
||||
|
||||
|
@ -263,40 +263,34 @@ export class View extends ViewEventHandler {
|
|||
// Sorted by their start position
|
||||
glyphs.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range));
|
||||
|
||||
let leftDecRange: Range | null = null;
|
||||
let rightDecRange: Range | null = null;
|
||||
const maxLane = GlyphMarginLane.Right;
|
||||
const lanes: (Range | undefined)[] = Array.from({ length: maxLane + 1 });
|
||||
let requiredLanes = 1;
|
||||
|
||||
for (const decoration of glyphs) {
|
||||
|
||||
if (decoration.lane === GlyphMarginLane.Left && (!leftDecRange || Range.compareRangesUsingEnds(leftDecRange, decoration.range) < 0)) {
|
||||
// assign only if the range of `decoration` ends after, which means it has a higher chance to overlap with the other lane
|
||||
leftDecRange = decoration.range;
|
||||
// assign only if the range of `decoration` ends after, which means it has a higher chance to overlap with the other lane
|
||||
if (!lanes[decoration.lane] || Range.compareRangesUsingEnds(lanes[decoration.lane]!, decoration.range) < 0) {
|
||||
lanes[decoration.lane] = decoration.range;
|
||||
}
|
||||
|
||||
if (decoration.lane === GlyphMarginLane.Right && (!rightDecRange || Range.compareRangesUsingEnds(rightDecRange, decoration.range) < 0)) {
|
||||
// assign only if the range of `decoration` ends after, which means it has a higher chance to overlap with the other lane
|
||||
rightDecRange = decoration.range;
|
||||
}
|
||||
|
||||
if (leftDecRange && rightDecRange) {
|
||||
|
||||
if (leftDecRange.endLineNumber < rightDecRange.startLineNumber) {
|
||||
// there's no chance for `leftDecRange` to ever intersect something going further
|
||||
leftDecRange = null;
|
||||
let requiredLanesHere = 0;
|
||||
for (let i = 1; i <= maxLane; i++) {
|
||||
const lane = lanes[i];
|
||||
if (!lane || lane.endLineNumber < decoration.range.startLineNumber) {
|
||||
lanes[i] = undefined;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (rightDecRange.endLineNumber < leftDecRange.startLineNumber) {
|
||||
// there's no chance for `rightDecRange` to ever intersect something going further
|
||||
rightDecRange = null;
|
||||
continue;
|
||||
}
|
||||
requiredLanesHere++;
|
||||
}
|
||||
|
||||
// leftDecRange and rightDecRange are intersecting or touching => we need two lanes
|
||||
return 2;
|
||||
requiredLanes = Math.max(requiredLanes, requiredLanesHere);
|
||||
if (requiredLanes === maxLane) {
|
||||
return requiredLanes;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
return requiredLanes;
|
||||
}
|
||||
|
||||
private _createPointerHandlerHelper(): IPointerHandlerHelper {
|
||||
|
|
|
@ -39,7 +39,8 @@ export enum OverviewRulerLane {
|
|||
*/
|
||||
export enum GlyphMarginLane {
|
||||
Left = 1,
|
||||
Right = 2
|
||||
Center = 2,
|
||||
Right = 3,
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -2206,7 +2206,7 @@ export class ModelDecorationGlyphMarginOptions {
|
|||
readonly position: model.GlyphMarginLane;
|
||||
|
||||
constructor(options: model.IModelDecorationGlyphMarginOptions | null | undefined) {
|
||||
this.position = options?.position ?? model.GlyphMarginLane.Left;
|
||||
this.position = options?.position ?? model.GlyphMarginLane.Center;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -360,7 +360,8 @@ export enum EndOfLineSequence {
|
|||
*/
|
||||
export enum GlyphMarginLane {
|
||||
Left = 1,
|
||||
Right = 2
|
||||
Center = 2,
|
||||
Right = 3
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1580,7 +1580,8 @@ declare namespace monaco.editor {
|
|||
*/
|
||||
export enum GlyphMarginLane {
|
||||
Left = 1,
|
||||
Right = 2
|
||||
Center = 2,
|
||||
Right = 3
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -475,7 +475,11 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi
|
|||
if (decorations) {
|
||||
for (const { options } of decorations) {
|
||||
const clz = options.glyphMarginClassName;
|
||||
if (clz && (!clz.includes('codicon-') || clz.includes('codicon-testing-') || clz.includes('codicon-merge-') || clz.includes('codicon-arrow-') || clz.includes('codicon-loading') || clz.includes('codicon-fold') || clz.includes('codicon-inline-chat'))) {
|
||||
if (!clz) {
|
||||
continue;
|
||||
}
|
||||
const hasSomeActionableCodicon = !(clz.includes('codicon-') || clz.startsWith('coverage-deco-')) || clz.includes('codicon-testing-') || clz.includes('codicon-merge-') || clz.includes('codicon-arrow-') || clz.includes('codicon-loading') || clz.includes('codicon-fold') || clz.includes('codicon-inline-chat');
|
||||
if (hasSomeActionableCodicon) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { autorun, derived, observableFromEvent } from 'vs/base/common/observable';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { IEditorContribution } from 'vs/editor/common/editorCommon';
|
||||
import { GlyphMarginLane, ITextModel } from 'vs/editor/common/model';
|
||||
import { localize } from 'vs/nls';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { FileCoverage } from 'vs/workbench/contrib/testing/common/testCoverage';
|
||||
import { ITestCoverageService } from 'vs/workbench/contrib/testing/common/testCoverageService';
|
||||
import { DetailType } from 'vs/workbench/contrib/testing/common/testTypes';
|
||||
|
||||
export class CodeCoverageDecorations extends Disposable implements IEditorContribution {
|
||||
private loadingCancellation?: CancellationTokenSource;
|
||||
private readonly displayedStore = this._register(new DisposableStore());
|
||||
|
||||
constructor(
|
||||
editor: ICodeEditor,
|
||||
@ITestCoverageService coverage: ITestCoverageService,
|
||||
@ILogService private readonly log: ILogService,
|
||||
) {
|
||||
super();
|
||||
|
||||
const modelObs = observableFromEvent(editor.onDidChangeModel, () => editor.getModel());
|
||||
|
||||
const fileCoverage = derived(reader => {
|
||||
const report = coverage.selected.read(reader);
|
||||
if (!report) {
|
||||
return;
|
||||
}
|
||||
|
||||
const model = modelObs.read(reader);
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
|
||||
return report.getUri(model.uri);
|
||||
});
|
||||
|
||||
this._register(autorun(reader => {
|
||||
const c = fileCoverage.read(reader);
|
||||
if (c) {
|
||||
this.apply(editor.getModel()!, c);
|
||||
} else {
|
||||
this.clear();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private async apply(model: ITextModel, coverage: FileCoverage) {
|
||||
const details = await this.loadDetails(coverage);
|
||||
if (!details) {
|
||||
return this.clear();
|
||||
}
|
||||
|
||||
const decorations: string[] = [];
|
||||
model.changeDecorations(e => {
|
||||
for (const detail of details) {
|
||||
const range = detail.location instanceof Range ? detail.location : Range.fromPositions(detail.location);
|
||||
if (detail.type === DetailType.Statement) {
|
||||
const cls = detail.count > 0 ? 'coverage-deco-hit' : 'coverage-deco-miss';
|
||||
decorations.push(e.addDecoration(range, {
|
||||
showIfCollapsed: false,
|
||||
glyphMargin: { position: GlyphMarginLane.Left },
|
||||
description: localize('testing.hitCount', 'Hit count: {0}', detail.count),
|
||||
glyphMarginClassName: `coverage-deco-gutter ${cls}`,
|
||||
className: `coverage-deco-inline ${cls}`,
|
||||
}));
|
||||
|
||||
if (detail.branches) {
|
||||
for (const branch of detail.branches) {
|
||||
const location = branch.location || range.getEndPosition();
|
||||
const branchRange = location instanceof Range ? location : Range.fromPositions(location);
|
||||
decorations.push(e.addDecoration(branchRange, {
|
||||
showIfCollapsed: false,
|
||||
glyphMargin: { position: GlyphMarginLane.Left },
|
||||
description: localize('testing.hitCount', 'Hit count: {0}', detail.count),
|
||||
glyphMarginClassName: `coverage-deco-gutter ${cls}`,
|
||||
className: `coverage-deco-inline ${cls}`,
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.displayedStore.add(toDisposable(() => {
|
||||
model.changeDecorations(e => {
|
||||
for (const decoration of decorations) {
|
||||
e.removeDecoration(decoration);
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
private clear() {
|
||||
this.loadingCancellation?.cancel();
|
||||
this.loadingCancellation = undefined;
|
||||
this.displayedStore.clear();
|
||||
}
|
||||
|
||||
private async loadDetails(coverage: FileCoverage) {
|
||||
const cts = this.loadingCancellation = new CancellationTokenSource();
|
||||
this.displayedStore.add(this.loadingCancellation);
|
||||
|
||||
try {
|
||||
const details = await coverage.details(this.loadingCancellation.token);
|
||||
if (!cts.token.isCancellationRequested) {
|
||||
return details;
|
||||
}
|
||||
} catch (e) {
|
||||
this.log.error('Error loading coverage details', e);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
|
@ -414,3 +414,43 @@
|
|||
.explorer-item-with-test-coverage .monaco-icon-label::after {
|
||||
margin-right: 12px; /* slightly reduce because the bars handle the scrollbar margin */
|
||||
}
|
||||
|
||||
/** -- coverage decorations */
|
||||
|
||||
.coverage-deco-gutter::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
right: 25%;
|
||||
left: 25%;
|
||||
}
|
||||
|
||||
.coverage-deco-gutter.coverage-deco-hit::before {
|
||||
background: var(--vscode-testing-coveredGutterBackground);
|
||||
}
|
||||
|
||||
.coverage-deco-gutter.coverage-deco-miss::before {
|
||||
background: var(--vscode-testing-uncoveredGutterBackground);
|
||||
}
|
||||
|
||||
.coverage-deco-gutter.coverage-deco-miss.coverage-deco-hit::before {
|
||||
background-image: linear-gradient(45deg,
|
||||
var(--vscode-testing-coveredGutterBackground) 25%,
|
||||
var(--vscode-testing-uncoveredGutterBackground) 25%,
|
||||
var(--vscode-testing-uncoveredGutterBackground) 50%,
|
||||
var(--vscode-testing-coveredGutterBackground) 50%,
|
||||
75%,
|
||||
var(--vscode-testing-uncoveredGutterBackground) 75%,
|
||||
var(--vscode-testing-uncoveredGutterBackground) 100%
|
||||
);
|
||||
background-size: 6px 6px;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.coverage-deco-inline.coverage-deco-hit {
|
||||
background: var(--vscode-testing-coveredBackground);
|
||||
}
|
||||
|
||||
.coverage-deco-inline.coverage-deco-miss {
|
||||
background: var(--vscode-testing-uncoveredBackground);
|
||||
}
|
||||
|
|
|
@ -754,11 +754,14 @@ abstract class RunTestDecoration {
|
|||
this.showContextMenu(e);
|
||||
break;
|
||||
case DefaultGutterClickAction.Debug:
|
||||
(alternateAction ? this.defaultRun() : this.defaultDebug());
|
||||
this.runWith(alternateAction ? TestRunProfileBitset.Run : TestRunProfileBitset.Debug);
|
||||
break;
|
||||
case DefaultGutterClickAction.Coverage:
|
||||
this.runWith(alternateAction ? TestRunProfileBitset.Debug : TestRunProfileBitset.Coverage);
|
||||
break;
|
||||
case DefaultGutterClickAction.Run:
|
||||
default:
|
||||
(alternateAction ? this.defaultDebug() : this.defaultRun());
|
||||
this.runWith(alternateAction ? TestRunProfileBitset.Debug : TestRunProfileBitset.Run);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -798,17 +801,10 @@ abstract class RunTestDecoration {
|
|||
*/
|
||||
abstract getContextMenuActions(): IReference<IAction[]>;
|
||||
|
||||
protected defaultRun() {
|
||||
protected runWith(profile: TestRunProfileBitset) {
|
||||
return this.testService.runTests({
|
||||
tests: this.tests.map(({ test }) => test),
|
||||
group: TestRunProfileBitset.Run,
|
||||
});
|
||||
}
|
||||
|
||||
protected defaultDebug() {
|
||||
return this.testService.runTests({
|
||||
tests: this.tests.map(({ test }) => test),
|
||||
group: TestRunProfileBitset.Debug,
|
||||
group: profile,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -823,6 +819,8 @@ abstract class RunTestDecoration {
|
|||
return localize('testing.gutterMsg.contextMenu', 'Click for test options');
|
||||
case DefaultGutterClickAction.Debug:
|
||||
return localize('testing.gutterMsg.debug', 'Click to debug tests, right click for more options');
|
||||
case DefaultGutterClickAction.Coverage:
|
||||
return localize('testing.gutterMsg.coverage', 'Click to run tests with coverage, right click for more options');
|
||||
case DefaultGutterClickAction.Run:
|
||||
default:
|
||||
return localize('testing.gutterMsg.run', 'Click to run tests, right click for more options');
|
||||
|
@ -835,19 +833,17 @@ abstract class RunTestDecoration {
|
|||
protected getTestContextMenuActions(test: InternalTestItem, resultItem?: TestResultItem): IReference<IAction[]> {
|
||||
const testActions: IAction[] = [];
|
||||
const capabilities = this.testProfileService.capabilitiesForTest(test);
|
||||
if (capabilities & TestRunProfileBitset.Run) {
|
||||
testActions.push(new Action('testing.gutter.run', localize('run test', 'Run Test'), undefined, undefined, () => this.testService.runTests({
|
||||
group: TestRunProfileBitset.Run,
|
||||
tests: [test],
|
||||
})));
|
||||
}
|
||||
|
||||
if (capabilities & TestRunProfileBitset.Debug) {
|
||||
testActions.push(new Action('testing.gutter.debug', localize('debug test', 'Debug Test'), undefined, undefined, () => this.testService.runTests({
|
||||
group: TestRunProfileBitset.Debug,
|
||||
tests: [test],
|
||||
})));
|
||||
}
|
||||
[
|
||||
{ bitset: TestRunProfileBitset.Run, label: localize('run test', 'Run Test') },
|
||||
{ bitset: TestRunProfileBitset.Debug, label: localize('debug test', 'Debug Test') },
|
||||
{ bitset: TestRunProfileBitset.Coverage, label: localize('coverage test', 'Run with Coverage') },
|
||||
].forEach(({ bitset, label }) => {
|
||||
if (capabilities & bitset) {
|
||||
testActions.push(new Action(`testing.gutter.${bitset}`, label, undefined, undefined,
|
||||
() => this.testService.runTests({ group: bitset, tests: [test] })));
|
||||
}
|
||||
});
|
||||
|
||||
if (capabilities & TestRunProfileBitset.HasNonDefaultProfile) {
|
||||
testActions.push(new Action('testing.runUsing', localize('testing.runUsing', 'Execute Using Profile...'), undefined, undefined, async () => {
|
||||
|
@ -924,17 +920,19 @@ class MultiRunTestDecoration extends RunTestDecoration implements ITestDecoratio
|
|||
super(tests, visible, model, codeEditorService, testService, contextMenuService, commandService, configurationService, testProfileService, contextKeyService, menuService);
|
||||
}
|
||||
|
||||
override getContextMenuActions() {
|
||||
public override getContextMenuActions() {
|
||||
const allActions: IAction[] = [];
|
||||
const canRun = this.tests.some(({ test }) => this.testProfileService.capabilitiesForTest(test) & TestRunProfileBitset.Run);
|
||||
if (canRun) {
|
||||
allActions.push(new Action('testing.gutter.runAll', localize('run all test', 'Run All Tests'), undefined, undefined, () => this.defaultRun()));
|
||||
}
|
||||
|
||||
const canDebug = this.tests.some(({ test }) => this.testProfileService.capabilitiesForTest(test) & TestRunProfileBitset.Debug);
|
||||
if (canDebug) {
|
||||
allActions.push(new Action('testing.gutter.debugAll', localize('debug all test', 'Debug All Tests'), undefined, undefined, () => this.defaultDebug()));
|
||||
}
|
||||
[
|
||||
{ bitset: TestRunProfileBitset.Run, label: localize('run all test', 'Run All Tests') },
|
||||
{ bitset: TestRunProfileBitset.Coverage, label: localize('run all test with coverage', 'Run All Tests with Coverage') },
|
||||
{ bitset: TestRunProfileBitset.Debug, label: localize('debug all test', 'Debug All Tests') },
|
||||
].forEach(({ bitset, label }, i) => {
|
||||
const canRun = this.tests.some(({ test }) => this.testProfileService.capabilitiesForTest(test) & bitset);
|
||||
if (canRun) {
|
||||
allActions.push(new Action(`testing.gutter.run${i}`, label, undefined, undefined, () => this.runWith(bitset)));
|
||||
}
|
||||
});
|
||||
|
||||
const testItems = this.tests.map((testItem): IMultiRunTest => ({
|
||||
currentLabel: testItem.test.item.label,
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import { Color, RGBA } from 'vs/base/common/color';
|
||||
import { localize } from 'vs/nls';
|
||||
import { contrastBorder, editorErrorForeground, editorForeground, editorInfoForeground, registerColor, transparent } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { contrastBorder, diffInserted, diffRemoved, editorErrorForeground, editorForeground, editorInfoForeground, registerColor, transparent } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { TestMessageType, TestResultState } from 'vs/workbench/contrib/testing/common/testTypes';
|
||||
|
||||
export const testingColorIconFailed = registerColor('testing.iconFailed', {
|
||||
|
@ -85,6 +85,34 @@ export const testingPeekMessageHeaderBackground = registerColor('testing.message
|
|||
hcLight: null
|
||||
}, localize('testing.messagePeekHeaderBackground', 'Color of the peek view borders and arrow when peeking a logged message.'));
|
||||
|
||||
export const testingCoveredBackground = registerColor('testing.coveredBackground', {
|
||||
dark: diffInserted,
|
||||
light: diffInserted,
|
||||
hcDark: null,
|
||||
hcLight: null
|
||||
}, localize('testing.coveredBackground', 'Background color of text that was covered.'));
|
||||
|
||||
export const testingCoveredGutterBackground = registerColor('testing.coveredGutterBackground', {
|
||||
dark: diffInserted,
|
||||
light: diffInserted,
|
||||
hcDark: null,
|
||||
hcLight: null
|
||||
}, localize('testing.coveredGutterBackground', 'Gutter color of regions where code was covered.'));
|
||||
|
||||
export const testingUncoveredBackground = registerColor('testing.uncoveredBackground', {
|
||||
dark: diffRemoved,
|
||||
light: diffRemoved,
|
||||
hcDark: null,
|
||||
hcLight: null
|
||||
}, localize('testing.uncoveredBackground', 'Background color of text that was not covered.'));
|
||||
|
||||
export const testingUncoveredGutterBackground = registerColor('testing.uncoveredGutterBackground', {
|
||||
dark: diffRemoved,
|
||||
light: diffRemoved,
|
||||
hcDark: null,
|
||||
hcLight: null
|
||||
}, localize('testing.uncoveredGutterBackground', 'Gutter color of regions where code not covered.'));
|
||||
|
||||
export const testMessageSeverityColors: {
|
||||
[K in TestMessageType]: {
|
||||
decorationForeground: string;
|
||||
|
|
|
@ -41,6 +41,7 @@ export const enum AutoOpenPeekViewWhen {
|
|||
export const enum DefaultGutterClickAction {
|
||||
Run = 'run',
|
||||
Debug = 'debug',
|
||||
Coverage = 'runWithCoverage',
|
||||
ContextMenu = 'contextMenu',
|
||||
}
|
||||
|
||||
|
@ -119,11 +120,13 @@ export const testingConfiguration: IConfigurationNode = {
|
|||
enum: [
|
||||
DefaultGutterClickAction.Run,
|
||||
DefaultGutterClickAction.Debug,
|
||||
DefaultGutterClickAction.Coverage,
|
||||
DefaultGutterClickAction.ContextMenu,
|
||||
],
|
||||
enumDescriptions: [
|
||||
localize('testing.defaultGutterClickAction.run', 'Run the test.'),
|
||||
localize('testing.defaultGutterClickAction.debug', 'Debug the test.'),
|
||||
localize('testing.defaultGutterClickAction.coverage', 'Run the test with coverage.'),
|
||||
localize('testing.defaultGutterClickAction.contextMenu', 'Open the context menu for more options.'),
|
||||
],
|
||||
default: DefaultGutterClickAction.Run,
|
||||
|
|
|
@ -13,6 +13,7 @@ export const enum Testing {
|
|||
ExplorerViewId = 'workbench.view.testing',
|
||||
OutputPeekContributionId = 'editor.contrib.testingOutputPeek',
|
||||
DecorationsContributionId = 'editor.contrib.testingDecorations',
|
||||
CoverageDecorationsContributionId = 'editor.contrib.coverageDecorations',
|
||||
CoverageViewId = 'workbench.view.testCoverage',
|
||||
|
||||
ResultsPanelId = 'workbench.panel.testResults',
|
||||
|
|
Loading…
Reference in New Issue