mirror of
https://github.com/microsoft/vscode.git
synced 2026-09-26 01:38:40 +01:00
Merge pull request #212610 from microsoft/connor4312/212196-5
editor: fix swapped condition in overlay widget
This commit is contained in:
@@ -188,7 +188,7 @@ function _wrapAsStandardKeyboardEvent(handler: (e: IKeyboardEvent) => void): (e:
|
||||
export const addStandardDisposableListener: IAddStandardDisposableListenerSignature = function addStandardDisposableListener(node: HTMLElement, type: string, handler: (event: any) => void, useCapture?: boolean): IDisposable {
|
||||
let wrapHandler = handler;
|
||||
|
||||
if (type === 'click' || type === 'mousedown') {
|
||||
if (type === 'click' || type === 'mousedown' || type === 'contextmenu') {
|
||||
wrapHandler = _wrapAsStandardMouseEvent(getWindow(node), handler);
|
||||
} else if (type === 'keydown' || type === 'keypress' || type === 'keyup') {
|
||||
wrapHandler = _wrapAsStandardKeyboardEvent(handler);
|
||||
|
||||
@@ -250,11 +250,21 @@ export interface IOverlayWidgetPosition {
|
||||
* The position preference for the overlay widget.
|
||||
*/
|
||||
preference: OverlayWidgetPositionPreference | IOverlayWidgetPositionCoordinates | null;
|
||||
|
||||
/**
|
||||
* When set, stacks with other overlay widgets with the same preference,
|
||||
* in an order determined by the ordinal value.
|
||||
*/
|
||||
stackOridinal?: number;
|
||||
}
|
||||
/**
|
||||
* An overlay widgets renders on top of the text.
|
||||
*/
|
||||
export interface IOverlayWidget {
|
||||
/**
|
||||
* Event fired when the widget layout changes.
|
||||
*/
|
||||
onDidLayout?: Event<void>;
|
||||
/**
|
||||
* Render this overlay widget in a location where it could overflow the editor's view dom node.
|
||||
*/
|
||||
|
||||
@@ -634,8 +634,7 @@ export class View extends ViewEventHandler {
|
||||
}
|
||||
|
||||
public layoutOverlayWidget(widgetData: IOverlayWidgetData): void {
|
||||
const newPreference = widgetData.position ? widgetData.position.preference : null;
|
||||
const shouldRender = this._overlayWidgets.setWidgetPosition(widgetData.widget, newPreference);
|
||||
const shouldRender = this._overlayWidgets.setWidgetPosition(widgetData.widget, widgetData.position);
|
||||
if (shouldRender) {
|
||||
this._scheduleRender();
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import 'vs/css!./overlayWidgets';
|
||||
import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode';
|
||||
import { IOverlayWidget, IOverlayWidgetPositionCoordinates, OverlayWidgetPositionPreference } from 'vs/editor/browser/editorBrowser';
|
||||
import { IOverlayWidget, IOverlayWidgetPosition, IOverlayWidgetPositionCoordinates, OverlayWidgetPositionPreference } from 'vs/editor/browser/editorBrowser';
|
||||
import { PartFingerprint, PartFingerprints, ViewPart } from 'vs/editor/browser/view/viewPart';
|
||||
import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/browser/view/renderingContext';
|
||||
import { ViewContext } from 'vs/editor/common/viewModel/viewContext';
|
||||
@@ -17,6 +17,7 @@ import * as dom from 'vs/base/browser/dom';
|
||||
interface IWidgetData {
|
||||
widget: IOverlayWidget;
|
||||
preference: OverlayWidgetPositionPreference | IOverlayWidgetPositionCoordinates | null;
|
||||
stack?: number;
|
||||
domNode: FastDomNode<HTMLElement>;
|
||||
}
|
||||
|
||||
@@ -109,14 +110,17 @@ export class ViewOverlayWidgets extends ViewPart {
|
||||
this._updateMaxMinWidth();
|
||||
}
|
||||
|
||||
public setWidgetPosition(widget: IOverlayWidget, preference: OverlayWidgetPositionPreference | IOverlayWidgetPositionCoordinates | null): boolean {
|
||||
public setWidgetPosition(widget: IOverlayWidget, position: IOverlayWidgetPosition | null): boolean {
|
||||
const widgetData = this._widgets[widget.getId()];
|
||||
if (widgetData.preference === preference) {
|
||||
const preference = position ? position.preference : null;
|
||||
const stack = position?.stackOridinal;
|
||||
if (widgetData.preference === preference && widgetData.stack === stack) {
|
||||
this._updateMaxMinWidth();
|
||||
return false;
|
||||
}
|
||||
|
||||
widgetData.preference = preference;
|
||||
widgetData.stack = stack;
|
||||
this.setShouldRender();
|
||||
this._updateMaxMinWidth();
|
||||
|
||||
@@ -150,7 +154,7 @@ export class ViewOverlayWidgets extends ViewPart {
|
||||
this._context.viewLayout.setOverlayWidgetsMinWidth(maxMinWidth);
|
||||
}
|
||||
|
||||
private _renderWidget(widgetData: IWidgetData): void {
|
||||
private _renderWidget(widgetData: IWidgetData, stackCoordinates: number[]): void {
|
||||
const domNode = widgetData.domNode;
|
||||
|
||||
if (widgetData.preference === null) {
|
||||
@@ -158,16 +162,29 @@ export class ViewOverlayWidgets extends ViewPart {
|
||||
return;
|
||||
}
|
||||
|
||||
if (widgetData.preference === OverlayWidgetPositionPreference.TOP_RIGHT_CORNER) {
|
||||
domNode.setTop(0);
|
||||
domNode.setRight((2 * this._verticalScrollbarWidth) + this._minimapWidth);
|
||||
} else if (widgetData.preference === OverlayWidgetPositionPreference.BOTTOM_RIGHT_CORNER) {
|
||||
const widgetHeight = domNode.domNode.clientHeight;
|
||||
domNode.setTop((this._editorHeight - widgetHeight - 2 * this._horizontalScrollbarHeight));
|
||||
domNode.setRight((2 * this._verticalScrollbarWidth) + this._minimapWidth);
|
||||
const maxRight = (2 * this._verticalScrollbarWidth) + this._minimapWidth;
|
||||
if (widgetData.preference === OverlayWidgetPositionPreference.TOP_RIGHT_CORNER || widgetData.preference === OverlayWidgetPositionPreference.BOTTOM_RIGHT_CORNER) {
|
||||
if (widgetData.preference === OverlayWidgetPositionPreference.BOTTOM_RIGHT_CORNER) {
|
||||
const widgetHeight = domNode.domNode.clientHeight;
|
||||
domNode.setTop((this._editorHeight - widgetHeight - 2 * this._horizontalScrollbarHeight));
|
||||
} else {
|
||||
domNode.setTop(0);
|
||||
}
|
||||
|
||||
if (widgetData.stack !== undefined) {
|
||||
domNode.setTop(stackCoordinates[widgetData.preference]);
|
||||
stackCoordinates[widgetData.preference] += domNode.domNode.clientWidth;
|
||||
} else {
|
||||
domNode.setRight(maxRight);
|
||||
}
|
||||
} else if (widgetData.preference === OverlayWidgetPositionPreference.TOP_CENTER) {
|
||||
domNode.setTop(0);
|
||||
domNode.domNode.style.right = '50%';
|
||||
if (widgetData.stack !== undefined) {
|
||||
domNode.setTop(stackCoordinates[OverlayWidgetPositionPreference.TOP_CENTER]);
|
||||
stackCoordinates[OverlayWidgetPositionPreference.TOP_CENTER] += domNode.domNode.clientHeight;
|
||||
} else {
|
||||
domNode.setTop(0);
|
||||
}
|
||||
} else {
|
||||
const { top, left } = widgetData.preference;
|
||||
const fixedOverflowWidgets = this._context.configuration.options.get(EditorOption.fixedOverflowWidgets);
|
||||
@@ -194,9 +211,12 @@ export class ViewOverlayWidgets extends ViewPart {
|
||||
this._domNode.setWidth(this._editorWidth);
|
||||
|
||||
const keys = Object.keys(this._widgets);
|
||||
const stackCoordinates = Array.from({ length: OverlayWidgetPositionPreference.TOP_CENTER + 1 }, () => 0);
|
||||
keys.sort((a, b) => (this._widgets[a].stack || 0) - (this._widgets[b].stack || 0));
|
||||
|
||||
for (let i = 0, len = keys.length; i < len; i++) {
|
||||
const widgetId = keys[i];
|
||||
this._renderWidget(this._widgets[widgetId]);
|
||||
this._renderWidget(this._widgets[widgetId], stackCoordinates);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,8 @@ import { HideUnchangedRegionsFeature } from 'vs/editor/browser/widget/diffEditor
|
||||
import { MovedBlocksLinesFeature } from 'vs/editor/browser/widget/diffEditor/features/movedBlocksLinesFeature';
|
||||
import { OverviewRulerFeature } from 'vs/editor/browser/widget/diffEditor/features/overviewRulerFeature';
|
||||
import { RevertButtonsFeature } from 'vs/editor/browser/widget/diffEditor/features/revertButtonsFeature';
|
||||
import { CSSStyle, ObservableElementSizeObserver, applyStyle, applyViewZones, bindContextKey, readHotReloadableExport, translatePosition } from 'vs/editor/browser/widget/diffEditor/utils';
|
||||
import { CSSStyle, ObservableElementSizeObserver, applyStyle, applyViewZones, readHotReloadableExport, translatePosition } from 'vs/editor/browser/widget/diffEditor/utils';
|
||||
import { bindContextKey } from 'vs/platform/observable/common/platformObservableUtils';
|
||||
import { IDiffEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
import { IDimension } from 'vs/editor/common/core/dimension';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
|
||||
@@ -8,7 +8,7 @@ import { findLast } from 'vs/base/common/arraysFind';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { isHotReloadEnabled, registerHotReloadHandler } from 'vs/base/common/hotReload';
|
||||
import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IObservable, IReader, ISettableObservable, autorun, autorunHandleChanges, autorunOpts, autorunWithStore, observableFromEvent, observableSignalFromEvent, observableValue, transaction } from 'vs/base/common/observable';
|
||||
import { IObservable, IReader, ISettableObservable, autorun, autorunHandleChanges, autorunOpts, autorunWithStore, observableSignalFromEvent, observableValue, transaction } from 'vs/base/common/observable';
|
||||
import { ElementSizeObserver } from 'vs/editor/browser/config/elementSizeObserver';
|
||||
import { ICodeEditor, IOverlayWidget, IViewZone } from 'vs/editor/browser/editorBrowser';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
@@ -16,8 +16,6 @@ import { Range } from 'vs/editor/common/core/range';
|
||||
import { DetailedLineRangeMapping } from 'vs/editor/common/diff/rangeMapping';
|
||||
import { IModelDeltaDecoration } from 'vs/editor/common/model';
|
||||
import { TextLength } from 'vs/editor/common/core/textLength';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ContextKeyValue, RawContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
|
||||
export function joinCombine<T>(arr1: readonly T[], arr2: readonly T[], keySelector: (val: T) => number, combine: (v1: T, v2: T) => T): readonly T[] {
|
||||
if (arr1.length === 0) {
|
||||
@@ -89,17 +87,6 @@ export function prependRemoveOnDispose(parent: HTMLElement, child: HTMLElement)
|
||||
});
|
||||
}
|
||||
|
||||
export function observableConfigValue<T>(key: string, defaultValue: T, configurationService: IConfigurationService): IObservable<T> {
|
||||
return observableFromEvent(
|
||||
(handleChange) => configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration(key)) {
|
||||
handleChange(e);
|
||||
}
|
||||
}),
|
||||
() => configurationService.getValue<T>(key) ?? defaultValue,
|
||||
);
|
||||
}
|
||||
|
||||
export class ObservableElementSizeObserver extends Disposable {
|
||||
private readonly elementSizeObserver: ElementSizeObserver;
|
||||
|
||||
@@ -440,13 +427,6 @@ function lengthBetweenPositions(position1: Position, position2: Position): TextL
|
||||
}
|
||||
}
|
||||
|
||||
export function bindContextKey<T extends ContextKeyValue>(key: RawContextKey<T>, service: IContextKeyService, computeValue: (reader: IReader) => T): IDisposable {
|
||||
const boundKey = key.bindTo(service);
|
||||
return autorunOpts({ debugName: () => `Set Context Key "${key.key}"` }, reader => {
|
||||
boundKey.set(computeValue(reader));
|
||||
});
|
||||
}
|
||||
|
||||
export function filterWithPrevious<T>(arr: T[], filter: (cur: T, prev: T | undefined) => boolean): T[] {
|
||||
let prev: T | undefined;
|
||||
return arr.filter(cur => {
|
||||
|
||||
@@ -64,6 +64,7 @@
|
||||
box-shadow: var(--vscode-editorStickyScroll-shadow) 0 3px 2px -2px;
|
||||
z-index: 4;
|
||||
background-color: var(--vscode-editorStickyScroll-background);
|
||||
right: initial !important;
|
||||
}
|
||||
|
||||
.monaco-editor .sticky-widget.peek {
|
||||
|
||||
@@ -9,7 +9,7 @@ import { equals } from 'vs/base/common/arrays';
|
||||
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { ThemeIcon } from 'vs/base/common/themables';
|
||||
import 'vs/css!./stickyScroll';
|
||||
import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition } from 'vs/editor/browser/editorBrowser';
|
||||
import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, OverlayWidgetPositionPreference } from 'vs/editor/browser/editorBrowser';
|
||||
import { getColumnOfNodeOffset } from 'vs/editor/browser/viewParts/lines/viewLine';
|
||||
import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget';
|
||||
import { EditorLayoutInfo, EditorOption, RenderLineNumbersType } from 'vs/editor/common/config/editorOptions';
|
||||
@@ -387,7 +387,8 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget {
|
||||
|
||||
getPosition(): IOverlayWidgetPosition | null {
|
||||
return {
|
||||
preference: null
|
||||
preference: OverlayWidgetPositionPreference.TOP_CENTER,
|
||||
stackOridinal: 10,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Vendored
+9
@@ -5387,12 +5387,21 @@ declare namespace monaco.editor {
|
||||
* The position preference for the overlay widget.
|
||||
*/
|
||||
preference: OverlayWidgetPositionPreference | IOverlayWidgetPositionCoordinates | null;
|
||||
/**
|
||||
* When set, stacks with other overlay widgets with the same preference,
|
||||
* in an order determined by the ordinal value.
|
||||
*/
|
||||
stackOridinal?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* An overlay widgets renders on top of the text.
|
||||
*/
|
||||
export interface IOverlayWidget {
|
||||
/**
|
||||
* Event fired when the widget layout changes.
|
||||
*/
|
||||
onDidLayout?: IEvent<void>;
|
||||
/**
|
||||
* Render this overlay widget in a location where it could overflow the editor's view dom node.
|
||||
*/
|
||||
|
||||
@@ -8,12 +8,13 @@ import { getStructuralKey } from 'vs/base/common/equals';
|
||||
import { Event, IValueWithChangeEvent } from 'vs/base/common/event';
|
||||
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { FileAccess } from 'vs/base/common/network';
|
||||
import { derived, IObservable, observableFromEvent } from 'vs/base/common/observable';
|
||||
import { derived, observableFromEvent } from 'vs/base/common/observable';
|
||||
import { ValueWithChangeEventFromObservable } from 'vs/base/common/observableInternal/utils';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { observableConfigValue } from 'vs/platform/observable/common/platformObservableUtils';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
|
||||
export const IAccessibilitySignalService = createDecorator<IAccessibilitySignalService>('accessibilitySignalService');
|
||||
@@ -201,7 +202,7 @@ export class AccessibilitySignalService extends Disposable implements IAccessibi
|
||||
private readonly _signalConfigValue = new CachedFunction((signal: AccessibilitySignal) => observableConfigValue<{
|
||||
sound: EnabledState;
|
||||
announcement: EnabledState;
|
||||
}>(signal.settingsKey, this.configurationService));
|
||||
}>(signal.settingsKey, { sound: 'off', announcement: 'off' }, this.configurationService));
|
||||
|
||||
private readonly _signalEnabledState = new CachedFunction(
|
||||
{ getCacheKey: getStructuralKey },
|
||||
@@ -589,13 +590,3 @@ export class AccessibilitySignal {
|
||||
});
|
||||
}
|
||||
|
||||
export function observableConfigValue<T>(key: string, configurationService: IConfigurationService): IObservable<T> {
|
||||
return observableFromEvent(
|
||||
(handleChange) => configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration(key)) {
|
||||
handleChange(e);
|
||||
}
|
||||
}),
|
||||
() => configurationService.getValue<T>(key),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { autorunOpts, IObservable, IReader, observableFromEvent } from 'vs/base/common/observable';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ContextKeyValue, RawContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
|
||||
/** Creates an observable update when a configuration key updates. */
|
||||
export function observableConfigValue<T>(key: string, defaultValue: T, configurationService: IConfigurationService): IObservable<T> {
|
||||
return observableFromEvent(
|
||||
(handleChange) => configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration(key)) {
|
||||
handleChange(e);
|
||||
}
|
||||
}),
|
||||
() => configurationService.getValue<T>(key) ?? defaultValue
|
||||
);
|
||||
}
|
||||
|
||||
/** Update the configuration key with a value derived from observables. */
|
||||
export function bindContextKey<T extends ContextKeyValue>(key: RawContextKey<T>, service: IContextKeyService, computeValue: (reader: IReader) => T): IDisposable {
|
||||
const boundKey = key.bindTo(service);
|
||||
return autorunOpts({ debugName: () => `Set Context Key "${key.key}"` }, reader => {
|
||||
boundKey.set(computeValue(reader));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';
|
||||
import { DetailedLineRangeMapping, RangeMapping } from 'vs/workbench/contrib/mergeEditor/browser/model/mapping';
|
||||
import { observableConfigValue } from 'vs/workbench/contrib/mergeEditor/browser/utils';
|
||||
import { observableConfigValue } from 'vs/platform/observable/common/platformObservableUtils';
|
||||
import { LineRange as DiffLineRange } from 'vs/editor/common/core/lineRange';
|
||||
|
||||
export interface IMergeDiffComputer {
|
||||
|
||||
@@ -6,10 +6,9 @@
|
||||
import { ArrayQueue, CompareResult } from 'vs/base/common/arrays';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IObservable, autorunOpts, observableFromEvent } from 'vs/base/common/observable';
|
||||
import { IObservable, autorunOpts } from 'vs/base/common/observable';
|
||||
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget';
|
||||
import { IModelDeltaDecoration } from 'vs/editor/common/model';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
|
||||
export function setStyle(
|
||||
@@ -156,13 +155,3 @@ export class PersistentStore<T> {
|
||||
}
|
||||
}
|
||||
|
||||
export function observableConfigValue<T>(key: string, defaultValue: T, configurationService: IConfigurationService): IObservable<T> {
|
||||
return observableFromEvent(
|
||||
(handleChange) => configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration(key)) {
|
||||
handleChange(e);
|
||||
}
|
||||
}),
|
||||
() => configurationService.getValue<T>(key) ?? defaultValue,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,8 @@ import { MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { DEFAULT_EDITOR_MAX_DIMENSIONS, DEFAULT_EDITOR_MIN_DIMENSIONS } from 'vs/workbench/browser/parts/editor/editor';
|
||||
import { observableConfigValue, setStyle } from 'vs/workbench/contrib/mergeEditor/browser/utils';
|
||||
import { setStyle } from 'vs/workbench/contrib/mergeEditor/browser/utils';
|
||||
import { observableConfigValue } from 'vs/platform/observable/common/platformObservableUtils';
|
||||
import { MergeEditorViewModel } from 'vs/workbench/contrib/mergeEditor/browser/view/viewModel';
|
||||
|
||||
export abstract class CodeEditorView extends Disposable {
|
||||
|
||||
@@ -39,7 +39,8 @@ import { readTransientState, writeTransientState } from 'vs/workbench/contrib/co
|
||||
import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput';
|
||||
import { IMergeEditorInputModel } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInputModel';
|
||||
import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel';
|
||||
import { deepMerge, observableConfigValue, PersistentStore, thenIfNotDisposed } from 'vs/workbench/contrib/mergeEditor/browser/utils';
|
||||
import { deepMerge, PersistentStore, thenIfNotDisposed } from 'vs/workbench/contrib/mergeEditor/browser/utils';
|
||||
import { observableConfigValue } from 'vs/platform/observable/common/platformObservableUtils';
|
||||
import { BaseCodeEditorView } from 'vs/workbench/contrib/mergeEditor/browser/view/editors/baseCodeEditorView';
|
||||
import { ScrollSynchronizer } from 'vs/workbench/contrib/mergeEditor/browser/view/scrollSynchronizer';
|
||||
import { MergeEditorViewModel } from 'vs/workbench/contrib/mergeEditor/browser/view/viewModel';
|
||||
|
||||
@@ -15,7 +15,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
|
||||
import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';
|
||||
import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel';
|
||||
import { InputNumber, ModifiedBaseRange, ModifiedBaseRangeState } from 'vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange';
|
||||
import { observableConfigValue } from 'vs/workbench/contrib/mergeEditor/browser/utils';
|
||||
import { observableConfigValue } from 'vs/platform/observable/common/platformObservableUtils';
|
||||
import { BaseCodeEditorView } from 'vs/workbench/contrib/mergeEditor/browser/view/editors/baseCodeEditorView';
|
||||
import { CodeEditorView } from 'vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView';
|
||||
import { InputCodeEditorView } from 'vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView';
|
||||
|
||||
@@ -4,16 +4,20 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
|
||||
import { ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { mapFindFirst } from 'vs/base/common/arraysFind';
|
||||
import { assert, assertNever } from 'vs/base/common/assert';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { Lazy } from 'vs/base/common/lazy';
|
||||
import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { autorun, derived, observableFromEvent, observableValue } from 'vs/base/common/observable';
|
||||
import { ThemeIcon } from 'vs/base/common/themables';
|
||||
import { ICodeEditor, MouseTargetType } from 'vs/editor/browser/editorBrowser';
|
||||
import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, MouseTargetType, OverlayWidgetPositionPreference } from 'vs/editor/browser/editorBrowser';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
@@ -21,20 +25,24 @@ import { IEditorContribution } from 'vs/editor/common/editorCommon';
|
||||
import { IModelDecorationOptions, InjectedTextCursorStops, InjectedTextOptions, ITextModel } from 'vs/editor/common/model';
|
||||
import { localize, localize2 } from 'vs/nls';
|
||||
import { Categories } from 'vs/platform/action/common/actionCommonCategories';
|
||||
import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
|
||||
import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { observableConfigValue } from 'vs/platform/observable/common/platformObservableUtils';
|
||||
import { IQuickInputService, QuickPickInput } from 'vs/platform/quickinput/common/quickInput';
|
||||
import * as coverUtils from 'vs/workbench/contrib/testing/browser/codeCoverageDisplayUtils';
|
||||
import { testingCoverageMissingBranch } from 'vs/workbench/contrib/testing/browser/icons';
|
||||
import { testingCoverageIcon, testingCoverageMissingBranch, testingFilterIcon, testingRerunIcon } from 'vs/workbench/contrib/testing/browser/icons';
|
||||
import { ManagedTestCoverageBars } from 'vs/workbench/contrib/testing/browser/testCoverageBars';
|
||||
import { getTestingConfiguration, TestingConfigKeys } from 'vs/workbench/contrib/testing/common/configuration';
|
||||
import { TestCommandId } from 'vs/workbench/contrib/testing/common/constants';
|
||||
import { FileCoverage } from 'vs/workbench/contrib/testing/common/testCoverage';
|
||||
import { ITestCoverageService } from 'vs/workbench/contrib/testing/common/testCoverageService';
|
||||
import { TestId } from 'vs/workbench/contrib/testing/common/testId';
|
||||
import { ITestService } from 'vs/workbench/contrib/testing/common/testService';
|
||||
import { CoverageDetails, DetailType, IDeclarationCoverage, IStatementCoverage } from 'vs/workbench/contrib/testing/common/testTypes';
|
||||
import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys';
|
||||
|
||||
@@ -52,7 +60,7 @@ export class CodeCoverageDecorations extends Disposable implements IEditorContri
|
||||
private loadingCancellation?: CancellationTokenSource;
|
||||
private readonly displayedStore = this._register(new DisposableStore());
|
||||
private readonly hoveredStore = this._register(new DisposableStore());
|
||||
private readonly summaryWidget: Lazy<CoverageSummaryWidget>;
|
||||
private readonly summaryWidget: Lazy<CoverageToolbarWidget>;
|
||||
private decorationIds = new Map<string, {
|
||||
detail: DetailRange;
|
||||
options: IModelDecorationOptions;
|
||||
@@ -65,11 +73,12 @@ export class CodeCoverageDecorations extends Disposable implements IEditorContri
|
||||
private readonly editor: ICodeEditor,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@ITestCoverageService coverage: ITestCoverageService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@ILogService private readonly log: ILogService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this.summaryWidget = new Lazy(() => this._register(instantiationService.createInstance(CoverageSummaryWidget, this.editor)));
|
||||
this.summaryWidget = new Lazy(() => this._register(instantiationService.createInstance(CoverageToolbarWidget, this.editor)));
|
||||
|
||||
const modelObs = observableFromEvent(editor.onDidChangeModel, () => editor.getModel());
|
||||
const configObs = observableFromEvent(editor.onDidChangeConfiguration, i => i);
|
||||
@@ -108,6 +117,16 @@ export class CodeCoverageDecorations extends Disposable implements IEditorContri
|
||||
}
|
||||
}));
|
||||
|
||||
const toolbarEnabled = observableConfigValue(TestingConfigKeys.CoverageToolbarEnabled, true, configurationService);
|
||||
this._register(autorun(reader => {
|
||||
const c = fileCoverage.read(reader);
|
||||
if (c && toolbarEnabled.read(reader)) {
|
||||
this.summaryWidget.value.setCoverage(c);
|
||||
} else {
|
||||
this.summaryWidget.rawValue?.setCoverage(undefined);
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(autorun(reader => {
|
||||
const c = fileCoverage.read(reader);
|
||||
if (c) {
|
||||
@@ -245,7 +264,6 @@ export class CodeCoverageDecorations extends Disposable implements IEditorContri
|
||||
}
|
||||
|
||||
this.displayedStore.clear();
|
||||
this.summaryWidget.value.setCoverage(coverage);
|
||||
|
||||
model.changeDecorations(e => {
|
||||
for (const detailRange of details.ranges) {
|
||||
@@ -309,8 +327,6 @@ export class CodeCoverageDecorations extends Disposable implements IEditorContri
|
||||
});
|
||||
|
||||
this.displayedStore.add(toDisposable(() => {
|
||||
this.summaryWidget.value.setCoverage(undefined);
|
||||
|
||||
model.changeDecorations(e => {
|
||||
for (const decoration of this.decorationIds.keys()) {
|
||||
e.removeDecoration(decoration);
|
||||
@@ -513,42 +529,81 @@ function wrapName(functionNameOrCode: string) {
|
||||
return wrapInBackticks(functionNameOrCode);
|
||||
}
|
||||
|
||||
class CoverageSummaryWidget implements IDisposable {
|
||||
class CoverageToolbarWidget extends Disposable implements IOverlayWidget {
|
||||
private current: FileCoverage | undefined;
|
||||
private registered = false;
|
||||
private readonly registration = new DisposableStore();
|
||||
|
||||
private isRunning = false;
|
||||
private readonly showStore = this._register(new DisposableStore());
|
||||
private readonly actionBar: ActionBar;
|
||||
private readonly _domNode = dom.h('div.coverage-summary-widget', [
|
||||
dom.h('div', [
|
||||
dom.h('span.bars@bars'),
|
||||
dom.h('span.stat@stat'),
|
||||
dom.h('a.toggleInline@toggleInline'),
|
||||
dom.h('a.perTestFilter@perTestFilter'),
|
||||
dom.h('span.toolbar@toolbar'),
|
||||
]),
|
||||
]);
|
||||
|
||||
private readonly bars: ManagedTestCoverageBars;
|
||||
|
||||
|
||||
constructor(
|
||||
private readonly editor: ICodeEditor,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IQuickInputService private readonly quickInputService: IQuickInputService,
|
||||
@ITestCoverageService private readonly testCoverageService: ITestCoverageService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IContextMenuService private readonly contextMenuService: IContextMenuService,
|
||||
@ITestService private readonly testService: ITestService,
|
||||
@IKeybindingService private readonly keybindingService: IKeybindingService,
|
||||
@IInstantiationService instaService: IInstantiationService,
|
||||
) {
|
||||
this._domNode.perTestFilter.ariaLabel = this._domNode.perTestFilter.title = coverUtils.labels.clickToChangeFiltering;
|
||||
this.bars = instaService.createInstance(ManagedTestCoverageBars, {
|
||||
super();
|
||||
|
||||
this.bars = this._register(instaService.createInstance(ManagedTestCoverageBars, {
|
||||
compact: false,
|
||||
overall: false,
|
||||
container: this._domNode.bars,
|
||||
});
|
||||
}));
|
||||
|
||||
const kb = keybindingService.lookupKeybinding(TOGGLE_INLINE_COMMAND_ID);
|
||||
if (kb) {
|
||||
this._domNode.toggleInline.title = `${TOGGLE_INLINE_COMMAND_TEXT} (${kb.getLabel()})`;
|
||||
}
|
||||
this.actionBar = this._register(instaService.createInstance(ActionBar, this._domNode.toolbar, {
|
||||
orientation: ActionsOrientation.HORIZONTAL,
|
||||
actionViewItemProvider: (action, options) => {
|
||||
const vm = new CodiconActionViewItem(undefined, action, options);
|
||||
if (action instanceof ActionWithIcon) {
|
||||
vm.themeIcon = action.icon;
|
||||
}
|
||||
return vm;
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
this._register(autorun(reader => {
|
||||
CodeCoverageDecorations.showInline.read(reader);
|
||||
this.setActions();
|
||||
}));
|
||||
|
||||
this._register(dom.addStandardDisposableListener(this._domNode.root, dom.EventType.CONTEXT_MENU, e => {
|
||||
this.contextMenuService.showContextMenu({
|
||||
menuId: MenuId.StickyScrollContext,
|
||||
getAnchor: () => e,
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public getId(): string {
|
||||
return 'coverage-summary-widget';
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public getDomNode(): HTMLElement {
|
||||
return this._domNode.root;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public getPosition(): IOverlayWidgetPosition | null {
|
||||
return {
|
||||
preference: OverlayWidgetPositionPreference.TOP_CENTER,
|
||||
stackOridinal: 9,
|
||||
};
|
||||
}
|
||||
|
||||
public setCoverage(coverage: FileCoverage | undefined) {
|
||||
@@ -556,32 +611,13 @@ class CoverageSummaryWidget implements IDisposable {
|
||||
this.bars.setCoverageInfo(coverage);
|
||||
|
||||
if (!coverage) {
|
||||
return this.unregister();
|
||||
return this.hide();
|
||||
}
|
||||
|
||||
const displayStat = coverUtils.calculateDisplayedStat(coverage, getTestingConfiguration(this.configurationService, TestingConfigKeys.CoveragePercent));
|
||||
this._domNode.stat.innerText = localize('testing.percentCoverage', '{0} Coverage', coverUtils.displayPercent(displayStat));
|
||||
|
||||
this._domNode.perTestFilter.classList.toggle('active', !!coverage.isForTest);
|
||||
if (coverage.isForTest) {
|
||||
const testItem = coverage.fromResult.getTestById(coverage.isForTest.id.toString());
|
||||
assert(!!testItem, 'got coverage for an unreported test');
|
||||
this._domNode.perTestFilter.style.display = 'inline';
|
||||
this._domNode.perTestFilter.innerText = coverUtils.labels.showingFilterFor(testItem.label);
|
||||
} else if (coverage.perTestData?.size) {
|
||||
this._domNode.perTestFilter.style.display = 'inline';
|
||||
this._domNode.perTestFilter.innerText = localize('testing.coverageForTestAvailable', "{0} test(s) in this file", coverage.perTestData.size);
|
||||
} else {
|
||||
this._domNode.perTestFilter.style.display = 'none';
|
||||
}
|
||||
|
||||
this.register();
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public dispose() {
|
||||
this.unregister();
|
||||
this.bars.dispose();
|
||||
this.setActions();
|
||||
this.show();
|
||||
}
|
||||
|
||||
private filterTest() {
|
||||
@@ -603,65 +639,144 @@ class CoverageSummaryWidget implements IDisposable {
|
||||
...tests.map(item => ({ label: coverUtils.getLabelForItem(result, item.isForTest!.id, commonPrefix), description: coverUtils.labels.percentCoverage(item.tpc), item })),
|
||||
];
|
||||
|
||||
// These handle the behavior that reveals the start of coverage when the
|
||||
// user picks from the quickpick. Scroll position is restored if the user
|
||||
// exits without picking an item, or picks "all tets".
|
||||
const scrollTop = this.editor.getScrollTop();
|
||||
const revealScrollCts = new MutableDisposable<CancellationTokenSource>();
|
||||
|
||||
this.quickInputService.pick(items, {
|
||||
activeItem: items.find((item): item is TItem => 'item' in item && item.item === this.current),
|
||||
placeHolder: coverUtils.labels.pickShowCoverage,
|
||||
onDidFocus: (entry) => {
|
||||
this.testCoverageService.filterToTest.set(entry.item?.isForTest!.id, undefined);
|
||||
if (!entry.item) {
|
||||
revealScrollCts.clear();
|
||||
this.editor.setScrollTop(scrollTop);
|
||||
this.testCoverageService.filterToTest.set(undefined, undefined);
|
||||
} else {
|
||||
const cts = revealScrollCts.value = new CancellationTokenSource();
|
||||
entry.item.details(cts.token).then(
|
||||
details => {
|
||||
const first = details.find(d => d.type === DetailType.Statement);
|
||||
if (!cts.token.isCancellationRequested && first) {
|
||||
this.editor.revealLineNearTop(first.location instanceof Position ? first.location.lineNumber : first.location.startLineNumber);
|
||||
}
|
||||
},
|
||||
() => { /* ignored */ }
|
||||
);
|
||||
this.testCoverageService.filterToTest.set(entry.item.isForTest!.id, undefined);
|
||||
}
|
||||
},
|
||||
}).then(selected => {
|
||||
if (!selected) {
|
||||
this.editor.setScrollTop(scrollTop);
|
||||
}
|
||||
|
||||
revealScrollCts.dispose();
|
||||
this.testCoverageService.filterToTest.set(selected ? selected.item?.isForTest!.id : previousSelection, undefined);
|
||||
});
|
||||
}
|
||||
|
||||
private register() {
|
||||
private setActions() {
|
||||
this.actionBar.clear();
|
||||
const coverage = this.current;
|
||||
if (!coverage) {
|
||||
return;
|
||||
}
|
||||
|
||||
const toggleAction = new ActionWithIcon(
|
||||
'toggleInline',
|
||||
CodeCoverageDecorations.showInline.get()
|
||||
? localize('testing.hideInlineCoverage', 'Hide Inline Coverage')
|
||||
: localize('testing.showInlineCoverage', 'Show Inline Coverage'),
|
||||
testingCoverageIcon,
|
||||
undefined,
|
||||
() => CodeCoverageDecorations.showInline.set(!CodeCoverageDecorations.showInline.get(), undefined),
|
||||
);
|
||||
|
||||
const kb = this.keybindingService.lookupKeybinding(TOGGLE_INLINE_COMMAND_ID);
|
||||
if (kb) {
|
||||
toggleAction.tooltip = `${TOGGLE_INLINE_COMMAND_TEXT} (${kb.getLabel()})`;
|
||||
}
|
||||
|
||||
this.actionBar.push(toggleAction);
|
||||
|
||||
if (coverage.isForTest) {
|
||||
const testItem = coverage.fromResult.getTestById(coverage.isForTest.id.toString());
|
||||
assert(!!testItem, 'got coverage for an unreported test');
|
||||
this.actionBar.push(new ActionWithIcon('perTestFilter',
|
||||
coverUtils.labels.showingFilterFor(testItem.label),
|
||||
testingFilterIcon,
|
||||
undefined,
|
||||
() => this.filterTest(),
|
||||
));
|
||||
} else if (coverage.perTestData?.size) {
|
||||
this.actionBar.push(new ActionWithIcon('perTestFilter',
|
||||
localize('testing.coverageForTestAvailable', "{0} test(s) in this file", coverage.perTestData.size),
|
||||
testingFilterIcon,
|
||||
undefined,
|
||||
() => this.filterTest(),
|
||||
));
|
||||
}
|
||||
|
||||
this.actionBar.push(new ActionWithIcon(
|
||||
'rerun',
|
||||
localize('testing.rerun', 'Rerun'),
|
||||
testingRerunIcon,
|
||||
!this.isRunning,
|
||||
() => this.rerunTest()
|
||||
));
|
||||
}
|
||||
|
||||
private show() {
|
||||
if (this.registered) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.registered = true;
|
||||
|
||||
let viewZoneId: string;
|
||||
const ds = this.showStore;
|
||||
|
||||
this.editor.addOverlayWidget(this);
|
||||
this.editor.changeViewZones(accessor => {
|
||||
viewZoneId = accessor.addZone({
|
||||
viewZoneId = accessor.addZone({ // make space for the widget
|
||||
afterLineNumber: 0,
|
||||
afterColumn: 0,
|
||||
domNode: this._domNode.root,
|
||||
domNode: document.createElement('div'),
|
||||
heightInPx: 30,
|
||||
ordinal: -1, // show before code lenses
|
||||
});
|
||||
});
|
||||
|
||||
this.registration.add(toDisposable(() => {
|
||||
ds.add(toDisposable(() => {
|
||||
this.registered = false;
|
||||
this.editor.removeOverlayWidget(this);
|
||||
this.editor.changeViewZones(accessor => {
|
||||
accessor.removeZone(viewZoneId);
|
||||
});
|
||||
this.registered = false;
|
||||
}));
|
||||
|
||||
this.registration.add(dom.addStandardDisposableListener(this._domNode.perTestFilter, 'click', () => {
|
||||
this.filterTest();
|
||||
}));
|
||||
|
||||
this.registration.add(this.configurationService.onDidChangeConfiguration(e => {
|
||||
ds.add(this.configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration(TestingConfigKeys.CoverageBarThresholds) || e.affectsConfiguration(TestingConfigKeys.CoveragePercent)) {
|
||||
this.setCoverage(this.current);
|
||||
}
|
||||
}));
|
||||
|
||||
this.registration.add(dom.addStandardDisposableListener(this._domNode.toggleInline, 'click', () => {
|
||||
CodeCoverageDecorations.showInline.set(!CodeCoverageDecorations.showInline.get(), undefined);
|
||||
}));
|
||||
|
||||
this.registration.add(autorun(reader => {
|
||||
this._domNode.toggleInline.innerText = CodeCoverageDecorations.showInline.read(reader)
|
||||
? localize('testing.hideInlineCoverage', 'Hide Inline Coverage')
|
||||
: localize('testing.showInlineCoverage', 'Show Inline Coverage');
|
||||
}));
|
||||
}
|
||||
|
||||
private unregister() {
|
||||
this.registration.clear();
|
||||
private rerunTest() {
|
||||
const current = this.current;
|
||||
if (current) {
|
||||
this.isRunning = true;
|
||||
this.setActions();
|
||||
this.testService.runResolvedTests(current.fromResult.request).finally(() => {
|
||||
this.isRunning = false;
|
||||
this.setActions();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private hide() {
|
||||
this.showStore.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -683,3 +798,47 @@ registerAction2(class ToggleInlineCoverage extends Action2 {
|
||||
CodeCoverageDecorations.showInline.set(!CodeCoverageDecorations.showInline.get(), undefined);
|
||||
}
|
||||
});
|
||||
|
||||
registerAction2(class ToggleCoverageToolbar extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: TestCommandId.CoverageToggleToolbar,
|
||||
title: localize2('testing.toggleToolbarTitle', "Toggle Coverage Toolbar"),
|
||||
metadata: {
|
||||
description: localize2('testing.toggleToolbarDesc', 'Toggle the sticky coverage bar in the editor.')
|
||||
},
|
||||
category: Categories.Test,
|
||||
toggled: {
|
||||
condition: TestingContextKeys.coverageToolbarEnabled,
|
||||
title: localize('cmd.toggle2', "Toggle Coverage Toolbar"),
|
||||
},
|
||||
menu: [
|
||||
{ id: MenuId.CommandPalette, when: TestingContextKeys.isTestCoverageOpen },
|
||||
{ id: MenuId.StickyScrollContext, when: TestingContextKeys.isTestCoverageOpen },
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
run(accessor: ServicesAccessor): void {
|
||||
const config = accessor.get(IConfigurationService);
|
||||
const value = getTestingConfiguration(config, TestingConfigKeys.CoverageToolbarEnabled);
|
||||
config.updateValue(TestingConfigKeys.CoverageToolbarEnabled, !value);
|
||||
}
|
||||
});
|
||||
|
||||
class ActionWithIcon extends Action {
|
||||
constructor(id: string, title: string, public readonly icon: ThemeIcon, enabled: boolean | undefined, run: () => void) {
|
||||
super(id, title, undefined, enabled, run);
|
||||
}
|
||||
}
|
||||
|
||||
class CodiconActionViewItem extends ActionViewItem {
|
||||
|
||||
public themeIcon?: ThemeIcon;
|
||||
|
||||
protected override updateLabel(): void {
|
||||
if (this.options.label && this.label && this.themeIcon) {
|
||||
dom.reset(this.label, renderIcon(this.themeIcon), this.action.label);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -404,50 +404,50 @@
|
||||
.coverage-summary-widget {
|
||||
color: var(--vscode-editor-foreground);
|
||||
z-index: 1;
|
||||
line-height: 25px;
|
||||
background: var(--vscode-editor-background);
|
||||
left: 0;
|
||||
width: 100%;
|
||||
box-shadow: var(--vscode-editorStickyScroll-shadow) 0 3px 2px -2px;
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid var(--vscode-menu-border);
|
||||
padding: 0 22px;
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
.toggleInline, .perTestFilter {
|
||||
border-left: 1px solid var(--vscode-menu-border);
|
||||
padding: 0 6px;
|
||||
}
|
||||
|
||||
.stat, .toggleInline {
|
||||
padding-right: 6px;
|
||||
}
|
||||
|
||||
> span, > a {
|
||||
display: inline;
|
||||
.btn {
|
||||
position: relative;
|
||||
padding: 0 6px;
|
||||
margin: 0 4px;
|
||||
padding: 0 4px;
|
||||
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-right: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
a {
|
||||
color: var(--vscode-textLink-foreground);
|
||||
cursor: pointer;
|
||||
.stat, .action-label {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
margin: 0 3px;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--vscode-textLink-activeForeground);
|
||||
.action-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
padding: 0 4px;
|
||||
|
||||
.codicon {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.toggleInline, .perTestFilter {
|
||||
border-left: 1px solid var(--vscode-menu-border);
|
||||
}
|
||||
}
|
||||
|
||||
.test-coverage-tree-per-test-switcher {
|
||||
|
||||
@@ -23,6 +23,7 @@ import { URI } from 'vs/base/common/uri';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { localize, localize2 } from 'vs/nls';
|
||||
import { Categories } from 'vs/platform/action/common/actionCommonCategories';
|
||||
import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
@@ -685,6 +686,7 @@ registerAction2(class TestCoverageChangePerTestFilterAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: TestCommandId.CoverageFilterToTest,
|
||||
category: Categories.Test,
|
||||
title: localize2('testing.changeCoverageFilter', 'Filter Coverage by Test...'),
|
||||
precondition: TestingContextKeys.hasPerTestCoverage,
|
||||
f1: true,
|
||||
|
||||
@@ -23,6 +23,7 @@ export const enum TestingConfigKeys {
|
||||
CoveragePercent = 'testing.displayedCoveragePercent',
|
||||
ShowCoverageInExplorer = 'testing.showCoverageInExplorer',
|
||||
CoverageBarThresholds = 'testing.coverageBarThresholds',
|
||||
CoverageToolbarEnabled = 'testing.coverageToolbarEnabled',
|
||||
}
|
||||
|
||||
export const enum AutoOpenTesting {
|
||||
@@ -190,6 +191,11 @@ export const testingConfiguration: IConfigurationNode = {
|
||||
green: { type: 'number', minimum: 0, maximum: 100, default: 90 },
|
||||
},
|
||||
},
|
||||
[TestingConfigKeys.CoverageToolbarEnabled]: {
|
||||
description: localize('testing.coverageToolbarEnabled', 'Controls whether the coverage toolbar is shown in the editor.'),
|
||||
type: 'boolean',
|
||||
default: false, // todo@connor4312: disabled by default until UI sync
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
@@ -214,6 +220,7 @@ export interface ITestingConfiguration {
|
||||
[TestingConfigKeys.CoveragePercent]: TestingDisplayedCoveragePercent;
|
||||
[TestingConfigKeys.ShowCoverageInExplorer]: boolean;
|
||||
[TestingConfigKeys.CoverageBarThresholds]: ITestingCoverageBarThresholds;
|
||||
[TestingConfigKeys.CoverageToolbarEnabled]: boolean;
|
||||
}
|
||||
|
||||
export const getTestingConfiguration = <K extends TestingConfigKeys>(config: IConfigurationService, key: K) => config.getValue<ITestingConfiguration[K]>(key);
|
||||
|
||||
@@ -68,6 +68,7 @@ export const enum TestCommandId {
|
||||
CoverageFilterToTest = 'testing.coverageFilterToTest',
|
||||
CoverageLastRun = 'testing.coverageLastRun',
|
||||
CoverageSelectedAction = 'testing.coverageSelected',
|
||||
CoverageToggleToolbar = 'testing.coverageToggleToolbar',
|
||||
CoverageViewChangeSorting = 'testing.coverageViewChangeSorting',
|
||||
DebugAction = 'testing.debug',
|
||||
DebugAllAction = 'testing.debugAll',
|
||||
|
||||
@@ -6,8 +6,11 @@
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IObservable, ISettableObservable, observableValue, transaction } from 'vs/base/common/observable';
|
||||
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { bindContextKey, observableConfigValue } from 'vs/platform/observable/common/platformObservableUtils';
|
||||
import { TestingConfigKeys } from 'vs/workbench/contrib/testing/common/configuration';
|
||||
import { Testing } from 'vs/workbench/contrib/testing/common/constants';
|
||||
import { TestCoverage } from 'vs/workbench/contrib/testing/common/testCoverage';
|
||||
import { TestId } from 'vs/workbench/contrib/testing/common/testId';
|
||||
@@ -45,8 +48,6 @@ export interface ITestCoverageService {
|
||||
|
||||
export class TestCoverageService extends Disposable implements ITestCoverageService {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
private readonly _isOpenKey: IContextKey<boolean>;
|
||||
private readonly _hasPerTestCoverage: IContextKey<boolean>;
|
||||
private readonly lastOpenCts = this._register(new MutableDisposable<CancellationTokenSource>());
|
||||
|
||||
public readonly selected = observableValue<TestCoverage | undefined>('testCoverage', undefined);
|
||||
@@ -55,11 +56,29 @@ export class TestCoverageService extends Disposable implements ITestCoverageServ
|
||||
constructor(
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@ITestResultService resultService: ITestResultService,
|
||||
@IConfigurationService configService: IConfigurationService,
|
||||
@IViewsService private readonly viewsService: IViewsService,
|
||||
) {
|
||||
super();
|
||||
this._isOpenKey = TestingContextKeys.isTestCoverageOpen.bindTo(contextKeyService);
|
||||
this._hasPerTestCoverage = TestingContextKeys.hasPerTestCoverage.bindTo(contextKeyService);
|
||||
|
||||
const toolbarConfig = observableConfigValue(TestingConfigKeys.CoverageToolbarEnabled, true, configService);
|
||||
this._register(bindContextKey(
|
||||
TestingContextKeys.coverageToolbarEnabled,
|
||||
contextKeyService,
|
||||
reader => toolbarConfig.read(reader),
|
||||
));
|
||||
|
||||
this._register(bindContextKey(
|
||||
TestingContextKeys.isTestCoverageOpen,
|
||||
contextKeyService,
|
||||
reader => !!this.selected.read(reader),
|
||||
));
|
||||
|
||||
this._register(bindContextKey(
|
||||
TestingContextKeys.hasPerTestCoverage,
|
||||
contextKeyService,
|
||||
reader => !!this.selected.read(reader)?.perTestCoverageIDs.size,
|
||||
));
|
||||
|
||||
this._register(resultService.onResultsChanged(evt => {
|
||||
if ('completed' in evt) {
|
||||
@@ -92,8 +111,6 @@ export class TestCoverageService extends Disposable implements ITestCoverageServ
|
||||
this.filterToTest.set(undefined, tx);
|
||||
this.selected.set(coverage, tx);
|
||||
});
|
||||
this._isOpenKey.set(true);
|
||||
this._hasPerTestCoverage.set(coverage.perTestCoverageIDs.size > 0);
|
||||
|
||||
if (focus && !cts.token.isCancellationRequested) {
|
||||
this.viewsService.openView(Testing.CoverageViewId, true);
|
||||
@@ -102,8 +119,6 @@ export class TestCoverageService extends Disposable implements ITestCoverageServ
|
||||
|
||||
/** @inheritdoc */
|
||||
public closeCoverage() {
|
||||
this._isOpenKey.set(false);
|
||||
this._hasPerTestCoverage.set(false);
|
||||
this.selected.set(undefined, undefined);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ export namespace TestingContextKeys {
|
||||
export const activeEditorHasTests = new RawContextKey('testing.activeEditorHasTests', false, { type: 'boolean', description: localize('testing.activeEditorHasTests', 'Indicates whether any tests are present in the current editor') });
|
||||
export const isTestCoverageOpen = new RawContextKey('testing.isTestCoverageOpen', false, { type: 'boolean', description: localize('testing.isTestCoverageOpen', 'Indicates whether a test coverage report is open') });
|
||||
export const hasPerTestCoverage = new RawContextKey('testing.hasPerTestCoverage', false, { type: 'boolean', description: localize('testing.hasPerTestCoverage', 'Indicates whether per-test coverage is available') });
|
||||
export const coverageToolbarEnabled = new RawContextKey('testing.coverageToolbarEnabled', true, { type: 'boolean', description: localize('testing.coverageToolbarEnabled', 'Indicates whether the coverage toolbar is enabled') });
|
||||
|
||||
export const capabilityToContextKey: { [K in TestRunProfileBitset]: RawContextKey<boolean> } = {
|
||||
[TestRunProfileBitset.Run]: hasRunnableTests,
|
||||
|
||||
+2
-11
@@ -5,7 +5,7 @@
|
||||
|
||||
import { importAMDNodeModule } from 'vs/amdX';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IObservable, autorun, keepObserved, observableFromEvent } from 'vs/base/common/observable';
|
||||
import { IObservable, autorun, keepObserved } from 'vs/base/common/observable';
|
||||
import { countEOL } from 'vs/editor/common/core/eolCounter';
|
||||
import { LineRange } from 'vs/editor/common/core/lineRange';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
@@ -15,6 +15,7 @@ import { TokenizationStateStore } from 'vs/editor/common/model/textModelTokens';
|
||||
import { IModelContentChange, IModelContentChangedEvent } from 'vs/editor/common/textModelEvents';
|
||||
import { ContiguousMultilineTokensBuilder } from 'vs/editor/common/tokens/contiguousMultilineTokensBuilder';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { observableConfigValue } from 'vs/platform/observable/common/platformObservableUtils';
|
||||
import { ArrayEdit, MonotonousIndexTransformer, SingleArrayEdit } from 'vs/workbench/services/textMate/browser/arrayOperation';
|
||||
import type { StateDeltas, TextMateTokenizationWorker } from 'vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker';
|
||||
import type { applyStateStackDiff, StateStack } from 'vscode-textmate';
|
||||
@@ -237,13 +238,3 @@ function changesToString(changes: IModelContentChange[]): string {
|
||||
return changes.map(c => Range.lift(c.range).toString() + ' => ' + c.text).join(' & ');
|
||||
}
|
||||
|
||||
function observableConfigValue<T>(key: string, defaultValue: T, configurationService: IConfigurationService): IObservable<T> {
|
||||
return observableFromEvent(
|
||||
(handleChange) => configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration(key)) {
|
||||
handleChange(e);
|
||||
}
|
||||
}),
|
||||
() => configurationService.getValue<T>(key) ?? defaultValue,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user