Merge pull request #212610 from microsoft/connor4312/212196-5

editor: fix swapped condition in overlay widget
This commit is contained in:
Connor Peet
2024-05-13 08:38:52 -07:00
committed by GitHub
24 changed files with 392 additions and 183 deletions
+1 -1
View File
@@ -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);
+10
View File
@@ -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.
*/
+1 -2
View File
@@ -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,
};
}
+9
View File
@@ -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,
@@ -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,
);
}