testing: add initial editor decorations
This is the first pass at decorations in-editor. This PR doesn't actually register the contribution, as it's not ready for selfhosting yet. This PR creates decorations that look like this. The idea is that coverage decorations in the glyph margin will always be visibile when there's coverage, and users can get coverage in their code via hover or shortcut, with the intention of making coverage unobtrusive and easy to run all the time. ![](https://memes.peet.io/img/24-01-8e61f4db-f115-4732-affe-59dea879a335.png) The notable thing is that there is now a third glyph margin row. I reworked some of the editor code to handle this. ![](https://memes.peet.io/img/24-01-f400369f-650c-4303-be65-e65903f8ad17.png) Some open questions: - The glyph margin coverage wants doesn't need to be full-width, should we add a new 'leftmost' glyph lane instead that's thinner? - Adding breakpoints in files with coverage is a little annoying since the breakpoint hint widget can expand the glyph margin on lines with coverage, and jump back over otherwise. Probably we should never decrease the number of lanes shown whenever the cursor is over the glyph margin. ![](https://memes.peet.io/img/24-01-79b53dd9-6fca-41dd-87b5-a113f9c25efb.gif)pull/202048/head
parent
b5d2084dfb
commit
0e743a2d91
|
@ -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