From df74b406337daa141f2c6b4a12d780dce9676b09 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Tue, 11 Feb 2020 12:04:09 +0100 Subject: [PATCH 001/109] Introduce a MinimapModel --- .../browser/viewParts/minimap/minimap.ts | 211 ++++++++++++++---- 1 file changed, 165 insertions(+), 46 deletions(-) diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index 5c8b974989d6..d949a6984a83 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -8,7 +8,7 @@ import * as dom from 'vs/base/browser/dom'; import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode'; import { GlobalMouseMoveMonitor, IStandardMouseMoveEventData, standardMouseMoveMerger } from 'vs/base/browser/globalMouseMoveMonitor'; import { CharCode } from 'vs/base/common/charCode'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; import * as strings from 'vs/base/common/strings'; import { ILine, RenderedLinesCollection } from 'vs/editor/browser/view/viewLayer'; @@ -24,7 +24,7 @@ import { MinimapTokensColorTracker } from 'vs/editor/common/viewModel/minimapTok import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/view/renderingContext'; import { ViewContext, EditorTheme } from 'vs/editor/common/view/viewContext'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; -import { ViewLineData } from 'vs/editor/common/viewModel/viewModel'; +import { ViewLineData, ViewModelDecoration, MinimapLinesRenderingData } from 'vs/editor/common/viewModel/viewModel'; import { minimapSelection, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, minimapBackground } from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { ModelDecorationMinimapOptions } from 'vs/editor/common/model/textModel'; @@ -32,7 +32,7 @@ import { Selection } from 'vs/editor/common/core/selection'; import { Color } from 'vs/base/common/color'; import { GestureEvent, EventType, Gesture } from 'vs/base/browser/touch'; import { MinimapCharRendererFactory } from 'vs/editor/browser/viewParts/minimap/minimapCharRendererFactory'; -import { MinimapPosition } from 'vs/editor/common/model'; +import { MinimapPosition, TextModelResolvedOptions } from 'vs/editor/common/model'; import { once } from 'vs/base/common/functional'; function getMinimapLineHeight(renderMinimap: RenderMinimap, scale: number): number { @@ -460,6 +460,132 @@ class MinimapBuffers { export class Minimap extends ViewPart { + private _model: MinimapModel; + private _actual: InnerMinimap; + + constructor(context: ViewContext) { + super(context); + + this._model = new MinimapModel(context); + this._actual = new InnerMinimap(context.configuration, context.theme, this._model); + } + + public dispose(): void { + this._actual.dispose(); + super.dispose(); + } + + public getDomNode(): FastDomNode { + return this._actual.getDomNode(); + } + + // ---- begin view event handlers + + public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { + return this._actual.onConfigurationChanged(e); + } + public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { + return this._actual.onCursorStateChanged(e); + } + public onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { + return this._actual.onDecorationsChanged(e); + } + public onFlushed(e: viewEvents.ViewFlushedEvent): boolean { + return this._actual.onFlushed(e); + } + public onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean { + return this._actual.onLinesChanged(e); + } + public onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean { + return this._actual.onLinesDeleted(e); + } + public onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean { + return this._actual.onLinesInserted(e); + } + public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { + return this._actual.onScrollChanged(e); + } + public onThemeChanged(e: viewEvents.ViewThemeChangedEvent): boolean { + this._context.model.invalidateMinimapColorCache(); + this._actual.onThemeChanged(e); + return true; + } + public onTokensChanged(e: viewEvents.ViewTokensChangedEvent): boolean { + return this._actual.onTokensChanged(e); + } + public onTokensColorsChanged(e: viewEvents.ViewTokensColorsChangedEvent): boolean { + return this._actual.onTokensColorsChanged(e); + } + public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { + return this._actual.onZonesChanged(e); + } + + // --- end event handlers + + public prepareRender(ctx: RenderingContext): void { + this._actual.prepareRender(ctx); + } + + public render(ctx: RestrictedRenderingContext): void { + this._actual.render(ctx); + } +} + +class MinimapModel { + + private readonly _context: ViewContext; + + constructor(context: ViewContext) { + this._context = context; + } + + public getLineCount(): number { + return this._context.model.getLineCount(); + } + + public getLineContent(lineNumber: number): string { + return this._context.model.getLineContent(lineNumber); + } + + public getLineMaxColumn(lineNumber: number): number { + return this._context.model.getLineMaxColumn(lineNumber); + } + + public getMinimapLinesRenderingData(startLineNumber: number, endLineNumber: number, needed: boolean[]): MinimapLinesRenderingData { + return this._context.model.getMinimapLinesRenderingData(startLineNumber, endLineNumber, needed); + } + + public getDecorationsInViewport(visibleRange: Range): ViewModelDecoration[] { + return this._context.model.getDecorationsInViewport(visibleRange); + } + + public getOptions(): TextModelResolvedOptions { + return this._context.model.getOptions(); + } + + public revealLineNumber(lineNumber: number): void { + this._context.privateViewEventBus.emit(new viewEvents.ViewRevealRangeRequestEvent( + 'mouse', + new Range(lineNumber, 1, lineNumber, 1), + viewEvents.VerticalRevealType.Center, + false, + ScrollType.Smooth + )); + } + + public setScrollTop(scrollTop: number): void { + this._context.viewLayout.setScrollPositionNow({ + scrollTop: scrollTop + }); + } +} + +class InnerMinimap extends Disposable { + + private readonly _configuration: IConfiguration; + private readonly _theme: EditorTheme; + private readonly _model: MinimapModel; + private readonly _tokensColorTracker: MinimapTokensColorTracker; private readonly _domNode: FastDomNode; private readonly _shadow: FastDomNode; @@ -483,14 +609,22 @@ export class Minimap extends ViewPart { private _gestureInProgress: boolean = false; private _buffers: MinimapBuffers | null; - constructor(context: ViewContext) { - super(context); + constructor( + configuration: IConfiguration, + theme: EditorTheme, + model: MinimapModel + ) { + super(); + + this._configuration = configuration; + this._theme = theme; + this._model = model; this._tokensColorTracker = MinimapTokensColorTracker.getInstance(); - this._options = new MinimapOptions(this._context.configuration, this._context.theme, this._tokensColorTracker); + this._options = new MinimapOptions(this._configuration, this._theme, this._tokensColorTracker); this._lastRenderData = null; this._buffers = null; - this._selectionColor = this._context.theme.getColor(minimapSelection); + this._selectionColor = this._theme.getColor(minimapSelection); this._domNode = createFastDomNode(document.createElement('div')); PartFingerprints.write(this._domNode, PartFingerprint.Minimap); @@ -543,15 +677,9 @@ export class Minimap extends ViewPart { const lineIndex = Math.floor(internalOffsetY / minimapLineHeight); let lineNumber = lineIndex + this._lastRenderData.renderedLayout.startLineNumber; - lineNumber = Math.min(lineNumber, this._context.model.getLineCount()); + lineNumber = Math.min(lineNumber, this._model.getLineCount()); - this._context.privateViewEventBus.emit(new viewEvents.ViewRevealRangeRequestEvent( - 'mouse', - new Range(lineNumber, 1, lineNumber, 1), - viewEvents.VerticalRevealType.Center, - false, - ScrollType.Smooth - )); + this._model.revealLineNumber(lineNumber); }); this._sliderMouseMoveMonitor = new GlobalMouseMoveMonitor(); @@ -575,16 +703,12 @@ export class Minimap extends ViewPart { if (platform.isWindows && mouseOrthogonalDelta > MOUSE_DRAG_RESET_DISTANCE) { // The mouse has wondered away from the scrollbar => reset dragging - this._context.viewLayout.setScrollPositionNow({ - scrollTop: initialSliderState.scrollTop - }); + this._model.setScrollTop(initialSliderState.scrollTop); return; } const mouseDelta = mouseMoveData.posy - initialMousePosition; - this._context.viewLayout.setScrollPositionNow({ - scrollTop: initialSliderState.getDesiredScrollTopFromDelta(mouseDelta) - }); + this._model.setScrollTop(initialSliderState.getDesiredScrollTopFromDelta(mouseDelta)); }, () => { this._slider.toggleClassName('active', false); @@ -623,9 +747,7 @@ export class Minimap extends ViewPart { private scrollDueToTouchEvent(touch: GestureEvent) { const startY = this._domNode.domNode.getBoundingClientRect().top; const scrollTop = this._lastRenderData!.renderedLayout.getDesiredScrollTopFromTouchLocation(touch.pageY - startY); - this._context.viewLayout.setScrollPositionNow({ - scrollTop: scrollTop - }); + this._model.setScrollTop(scrollTop); } public dispose(): void { @@ -684,7 +806,7 @@ export class Minimap extends ViewPart { } private _onOptionsMaybeChanged(): boolean { - const opts = new MinimapOptions(this._context.configuration, this._context.theme, this._tokensColorTracker); + const opts = new MinimapOptions(this._configuration, this._theme, this._tokensColorTracker); if (this._options.equals(opts)) { return false; } @@ -706,6 +828,10 @@ export class Minimap extends ViewPart { this._renderDecorations = true; return true; } + public onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { + this._renderDecorations = true; + return true; + } public onFlushed(e: viewEvents.ViewFlushedEvent): boolean { this._lastRenderData = null; return true; @@ -732,6 +858,12 @@ export class Minimap extends ViewPart { this._renderDecorations = true; return true; } + public onThemeChanged(e: viewEvents.ViewThemeChangedEvent): boolean { + this._selectionColor = this._theme.getColor(minimapSelection); + this._renderDecorations = true; + this._onOptionsMaybeChanged(); + return true; + } public onTokensChanged(e: viewEvents.ViewTokensChangedEvent): boolean { if (this._lastRenderData) { return this._lastRenderData.onTokensChanged(e); @@ -748,19 +880,6 @@ export class Minimap extends ViewPart { return true; } - public onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { - this._renderDecorations = true; - return true; - } - - public onThemeChanged(e: viewEvents.ViewThemeChangedEvent): boolean { - this._context.model.invalidateMinimapColorCache(); - this._selectionColor = this._context.theme.getColor(minimapSelection); - this._renderDecorations = true; - this._onOptionsMaybeChanged(); - return true; - } - // --- end event handlers public prepareRender(ctx: RenderingContext): void { @@ -787,7 +906,7 @@ export class Minimap extends ViewPart { renderingCtx.visibleRange.endLineNumber, renderingCtx.viewportHeight, (renderingCtx.viewportData.whitespaceViewportData.length > 0), - this._context.model.getLineCount(), + this._model.getLineCount(), renderingCtx.scrollTop, renderingCtx.scrollHeight, this._lastRenderData ? this._lastRenderData.renderedLayout : null @@ -810,12 +929,12 @@ export class Minimap extends ViewPart { private renderDecorations(layout: MinimapLayout) { if (this._renderDecorations) { this._renderDecorations = false; - const decorations = this._context.model.getDecorationsInViewport(new Range(layout.startLineNumber, 1, layout.endLineNumber, this._context.model.getLineMaxColumn(layout.endLineNumber))); + const decorations = this._model.getDecorationsInViewport(new Range(layout.startLineNumber, 1, layout.endLineNumber, this._model.getLineMaxColumn(layout.endLineNumber))); const { renderMinimap, canvasInnerWidth, canvasInnerHeight } = this._options; const lineHeight = getMinimapLineHeight(renderMinimap, this._options.fontScale); const characterWidth = getMinimapCharWidth(renderMinimap, this._options.fontScale); - const tabSize = this._context.model.getOptions().tabSize; + const tabSize = this._model.getOptions().tabSize; const canvasContext = this._decorationsCanvas.domNode.getContext('2d')!; canvasContext.clearRect(0, 0, canvasInnerWidth, canvasInnerHeight); @@ -837,7 +956,7 @@ export class Minimap extends ViewPart { continue; } - const decorationColor = (decoration.options.minimap).getColor(this._context.theme); + const decorationColor = (decoration.options.minimap).getColor(this._theme); for (let line = decoration.range.startLineNumber; line <= decoration.range.endLineNumber; line++) { switch (decoration.options.minimap.position) { @@ -877,7 +996,7 @@ export class Minimap extends ViewPart { let lineIndexToXOffset = lineOffsetMap.get(lineNumber); const isFirstDecorationForLine = !lineIndexToXOffset; if (!lineIndexToXOffset) { - const lineData = this._context.model.getLineContent(lineNumber); + const lineData = this._model.getLineContent(lineNumber); lineIndexToXOffset = [MINIMAP_GUTTER_WIDTH]; for (let i = 1; i < lineData.length + 1; i++) { const charCode = lineData.charCodeAt(i - 1); @@ -944,7 +1063,7 @@ export class Minimap extends ViewPart { } // Render untouched lines by using last rendered data. - let [_dirtyY1, _dirtyY2, needed] = Minimap._renderUntouchedLines( + let [_dirtyY1, _dirtyY2, needed] = InnerMinimap._renderUntouchedLines( imageData, startLineNumber, endLineNumber, @@ -953,7 +1072,7 @@ export class Minimap extends ViewPart { ); // Fetch rendering info from view model for rest of lines that need rendering. - const lineInfo = this._context.model.getMinimapLinesRenderingData(startLineNumber, endLineNumber, needed); + const lineInfo = this._model.getMinimapLinesRenderingData(startLineNumber, endLineNumber, needed); const tabSize = lineInfo.tabSize; const background = this._options.backgroundColor; const useLighterFont = this._tokensColorTracker.backgroundIsLight(); @@ -963,7 +1082,7 @@ export class Minimap extends ViewPart { const renderedLines: MinimapLine[] = []; for (let lineIndex = 0, lineCount = endLineNumber - startLineNumber + 1; lineIndex < lineCount; lineIndex++) { if (needed[lineIndex]) { - Minimap._renderLine( + InnerMinimap._renderLine( imageData, background, useLighterFont, From 461d7b8d9c86313b7759cb70c4f729d77dd48e61 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 11 Feb 2020 17:05:23 +0100 Subject: [PATCH 002/109] wip: extract documentation ext point --- build/lib/i18n.resources.json | 4 +++ .../common/codeActions.contribution.ts | 4 --- .../common/documentation.contribution.ts | 25 +++++++++++++++++++ .../common/documentationContribution.ts | 3 +-- .../common/documentationExtensionPoint.ts | 0 src/vs/workbench/workbench.common.main.ts | 3 +++ 6 files changed, 33 insertions(+), 6 deletions(-) create mode 100644 src/vs/workbench/contrib/documentation/common/documentation.contribution.ts rename src/vs/workbench/contrib/{codeActions => documentation}/common/documentationContribution.ts (95%) rename src/vs/workbench/contrib/{codeActions => documentation}/common/documentationExtensionPoint.ts (100%) diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index 31446bfe4d92..4ec85f30c566 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -62,6 +62,10 @@ "name": "vs/workbench/contrib/debug", "project": "vscode-workbench" }, + { + "name": "vs/workbench/contrib/documentation", + "project": "vscode-workbench" + }, { "name": "vs/workbench/contrib/emmet", "project": "vscode-workbench" diff --git a/src/vs/workbench/contrib/codeActions/common/codeActions.contribution.ts b/src/vs/workbench/contrib/codeActions/common/codeActions.contribution.ts index c7969c9d1c0b..ccdbb435b250 100644 --- a/src/vs/workbench/contrib/codeActions/common/codeActions.contribution.ts +++ b/src/vs/workbench/contrib/codeActions/common/codeActions.contribution.ts @@ -10,12 +10,9 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { CodeActionsContribution, editorConfiguration } from 'vs/workbench/contrib/codeActions/common/codeActionsContribution'; import { CodeActionsExtensionPoint, codeActionsExtensionPointDescriptor } from 'vs/workbench/contrib/codeActions/common/codeActionsExtensionPoint'; -import { CodeActionDocumentationContribution } from 'vs/workbench/contrib/codeActions/common/documentationContribution'; -import { DocumentationExtensionPoint, documentationExtensionPointDescriptor } from 'vs/workbench/contrib/codeActions/common/documentationExtensionPoint'; import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; const codeActionsExtensionPoint = ExtensionsRegistry.registerExtensionPoint(codeActionsExtensionPointDescriptor); -const documentationExtensionPoint = ExtensionsRegistry.registerExtensionPoint(documentationExtensionPointDescriptor); Registry.as(Extensions.Configuration) .registerConfiguration(editorConfiguration); @@ -25,7 +22,6 @@ class WorkbenchConfigurationContribution { @IInstantiationService instantiationService: IInstantiationService, ) { instantiationService.createInstance(CodeActionsContribution, codeActionsExtensionPoint); - instantiationService.createInstance(CodeActionDocumentationContribution, documentationExtensionPoint); } } diff --git a/src/vs/workbench/contrib/documentation/common/documentation.contribution.ts b/src/vs/workbench/contrib/documentation/common/documentation.contribution.ts new file mode 100644 index 000000000000..13f09eee1872 --- /dev/null +++ b/src/vs/workbench/contrib/documentation/common/documentation.contribution.ts @@ -0,0 +1,25 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { DocumentationContribution } from 'vs/workbench/contrib/documentation/common/documentationContribution'; +import { DocumentationExtensionPoint, documentationExtensionPointDescriptor } from 'vs/workbench/contrib/documentation/common/documentationExtensionPoint'; +import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; + +const documentationExtensionPoint = ExtensionsRegistry.registerExtensionPoint(documentationExtensionPointDescriptor); + +class WorkbenchConfigurationContribution { + constructor( + @IInstantiationService instantiationService: IInstantiationService, + ) { + instantiationService.createInstance(DocumentationContribution, documentationExtensionPoint); + } +} + +Registry.as(WorkbenchExtensions.Workbench) + .registerWorkbenchContribution(WorkbenchConfigurationContribution, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/codeActions/common/documentationContribution.ts b/src/vs/workbench/contrib/documentation/common/documentationContribution.ts similarity index 95% rename from src/vs/workbench/contrib/codeActions/common/documentationContribution.ts rename to src/vs/workbench/contrib/documentation/common/documentationContribution.ts index 9c2b75c277dc..57547e37c1df 100644 --- a/src/vs/workbench/contrib/codeActions/common/documentationContribution.ts +++ b/src/vs/workbench/contrib/documentation/common/documentationContribution.ts @@ -15,8 +15,7 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IExtensionPoint } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { DocumentationExtensionPoint } from './documentationExtensionPoint'; - -export class CodeActionDocumentationContribution extends Disposable implements IWorkbenchContribution, modes.CodeActionProvider { +export class DocumentationContribution extends Disposable implements IWorkbenchContribution, modes.CodeActionProvider { private contributions: { title: string; diff --git a/src/vs/workbench/contrib/codeActions/common/documentationExtensionPoint.ts b/src/vs/workbench/contrib/documentation/common/documentationExtensionPoint.ts similarity index 100% rename from src/vs/workbench/contrib/codeActions/common/documentationExtensionPoint.ts rename to src/vs/workbench/contrib/documentation/common/documentationExtensionPoint.ts diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 9debc489ee12..ee4edc24c591 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -272,6 +272,9 @@ import 'vs/workbench/contrib/userDataSync/browser/userDataSync.contribution'; // Code Actions import 'vs/workbench/contrib/codeActions/common/codeActions.contribution'; +// Documentation +import 'vs/workbench/contrib/documentation/common/documentation.contribution'; + // Timeline import 'vs/workbench/contrib/timeline/browser/timeline.contribution'; From 8d9be7fb505b8b5accc8e32c747abaf3abf82ae3 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 12 Feb 2020 00:15:57 +0100 Subject: [PATCH 003/109] First cut at minimap sampling lines --- .../browser/viewParts/minimap/minimap.ts | 444 ++++++++++++------ .../common/viewModel/splitLinesCollection.ts | 2 +- src/vs/editor/common/viewModel/viewModel.ts | 15 +- .../editor/common/viewModel/viewModelImpl.ts | 10 +- 4 files changed, 311 insertions(+), 160 deletions(-) diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index d949a6984a83..f8b400f81613 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -24,7 +24,7 @@ import { MinimapTokensColorTracker } from 'vs/editor/common/viewModel/minimapTok import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/view/renderingContext'; import { ViewContext, EditorTheme } from 'vs/editor/common/view/viewContext'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; -import { ViewLineData, ViewModelDecoration, MinimapLinesRenderingData } from 'vs/editor/common/viewModel/viewModel'; +import { ViewLineData, ViewModelDecoration } from 'vs/editor/common/viewModel/viewModel'; import { minimapSelection, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, minimapBackground } from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { ModelDecorationMinimapOptions } from 'vs/editor/common/model/textModel'; @@ -35,22 +35,6 @@ import { MinimapCharRendererFactory } from 'vs/editor/browser/viewParts/minimap/ import { MinimapPosition, TextModelResolvedOptions } from 'vs/editor/common/model'; import { once } from 'vs/base/common/functional'; -function getMinimapLineHeight(renderMinimap: RenderMinimap, scale: number): number { - if (renderMinimap === RenderMinimap.Text) { - return Constants.BASE_CHAR_HEIGHT * scale; - } - // RenderMinimap.Blocks - return (Constants.BASE_CHAR_HEIGHT + 1) * scale; -} - -function getMinimapCharWidth(renderMinimap: RenderMinimap, scale: number): number { - if (renderMinimap === RenderMinimap.Text) { - return Constants.BASE_CHAR_WIDTH * scale; - } - // RenderMinimap.Blocks - return Constants.BASE_CHAR_WIDTH * scale; -} - /** * The orthogonal distance to the slider at which dragging "resets". This implements "snapping" */ @@ -60,6 +44,8 @@ const GUTTER_DECORATION_WIDTH = 2; class MinimapOptions { + public readonly isSampling: boolean; + public readonly renderMinimap: RenderMinimap; public readonly scrollBeyondLastLine: boolean; @@ -109,17 +95,21 @@ class MinimapOptions { public readonly backgroundColor: RGBA8; - constructor(configuration: IConfiguration, theme: EditorTheme, tokensColorTracker: MinimapTokensColorTracker) { + public readonly minimapLineHeight: number; + public readonly minimapCharWidth: number; + + constructor(configuration: IConfiguration, theme: EditorTheme, tokensColorTracker: MinimapTokensColorTracker, isSampling: boolean) { const options = configuration.options; const pixelRatio = options.get(EditorOption.pixelRatio); const layoutInfo = options.get(EditorOption.layoutInfo); const fontInfo = options.get(EditorOption.fontInfo); + this.isSampling = isSampling; this.renderMinimap = layoutInfo.renderMinimap | 0; this.scrollBeyondLastLine = options.get(EditorOption.scrollBeyondLastLine); const minimapOpts = options.get(EditorOption.minimap); this.showSlider = minimapOpts.showSlider; - this.fontScale = Math.round(minimapOpts.scale * pixelRatio); + this.fontScale = (isSampling ? 1 : Math.round(minimapOpts.scale * pixelRatio)); this.charRenderer = once(() => MinimapCharRendererFactory.create(this.fontScale, fontInfo.fontFamily)); this.pixelRatio = pixelRatio; this.typicalHalfwidthCharacterWidth = fontInfo.typicalHalfwidthCharacterWidth; @@ -135,6 +125,10 @@ class MinimapOptions { this.canvasOuterHeight = this.canvasInnerHeight / pixelRatio; this.backgroundColor = MinimapOptions._getMinimapBackground(theme, tokensColorTracker); + + const baseCharHeight = (isSampling ? 1 : this.renderMinimap === RenderMinimap.Text ? Constants.BASE_CHAR_HEIGHT : Constants.BASE_CHAR_HEIGHT + 1); + this.minimapLineHeight = baseCharHeight * this.fontScale; + this.minimapCharWidth = Constants.BASE_CHAR_WIDTH * this.fontScale; } private static _getMinimapBackground(theme: EditorTheme, tokensColorTracker: MinimapTokensColorTracker): RGBA8 { @@ -146,7 +140,8 @@ class MinimapOptions { } public equals(other: MinimapOptions): boolean { - return (this.renderMinimap === other.renderMinimap + return (this.isSampling === other.isSampling + && this.renderMinimap === other.renderMinimap && this.scrollBeyondLastLine === other.scrollBeyondLastLine && this.showSlider === other.showSlider && this.pixelRatio === other.pixelRatio @@ -229,6 +224,7 @@ class MinimapLayout { public static create( options: MinimapOptions, + samplingRatio: number, viewportStartLineNumber: number, viewportEndLineNumber: number, viewportHeight: number, @@ -238,11 +234,23 @@ class MinimapLayout { scrollHeight: number, previousLayout: MinimapLayout | null ): MinimapLayout { + // console.log(`create - ${viewportStartLineNumber}, ${viewportEndLineNumber}, ${viewportHeight}, ${viewportContainsWhitespaceGaps}, ${lineCount}, ${scrollTop}, ${scrollHeight}`); const pixelRatio = options.pixelRatio; - const minimapLineHeight = getMinimapLineHeight(options.renderMinimap, options.fontScale); + const minimapLineHeight = options.minimapLineHeight; const minimapLinesFitting = Math.floor(options.canvasInnerHeight / minimapLineHeight); const lineHeight = options.lineHeight; + if (options.isSampling) { + const expectedViewportLineCount = viewportHeight / lineHeight; + const sliderHeight = Math.max(1, Math.floor(expectedViewportLineCount * minimapLineHeight / pixelRatio / samplingRatio)); + const maxMinimapSliderTop = Math.max(0, options.minimapHeight - sliderHeight); + // The slider can move from 0 to `maxMinimapSliderTop` + // in the same way `scrollTop` can move from 0 to `scrollHeight` - `viewportHeight`. + const computedSliderRatio = (maxMinimapSliderTop) / (scrollHeight - viewportHeight); + const sliderTop = (scrollTop * computedSliderRatio); + return new MinimapLayout(scrollTop, scrollHeight, computedSliderRatio, sliderTop, sliderHeight, 1, lineCount); + } + // The visible line count in a viewport can change due to a number of reasons: // a) with the same viewport width, different scroll positions can result in partial lines being visible: // e.g. for a line height of 20, and a viewport height of 600 @@ -400,8 +408,8 @@ class RenderData { public onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): void { this._renderedLines.onLinesInserted(e.fromLineNumber, e.toLineNumber); } - public onTokensChanged(e: viewEvents.ViewTokensChangedEvent): boolean { - return this._renderedLines.onTokensChanged(e.ranges); + public onTokensChanged(ranges: { fromLineNumber: number; toLineNumber: number; }[]): boolean { + return this._renderedLines.onTokensChanged(ranges); } } @@ -458,16 +466,63 @@ class MinimapBuffers { } } -export class Minimap extends ViewPart { +export interface IMinimapModel { + readonly tokensColorTracker: MinimapTokensColorTracker; + readonly options: MinimapOptions; + + getLineCount(): number; + getLineContent(lineNumber: number): string; + getMinimapLinesRenderingData(startLineNumber: number, endLineNumber: number, needed: boolean[]): (ViewLineData | null)[]; + getSelections(): Selection[]; + getMinimapDecorationsInViewport(startLineNumber: number, endLineNumber: number): ViewModelDecoration[]; + getOptions(): TextModelResolvedOptions; + revealLineNumber(lineNumber: number): void; + setScrollTop(scrollTop: number): void; +} + +export interface IMinimapRenderingContext { + readonly samplingRatio: number; + readonly viewportContainsWhitespaceGaps: boolean; + + readonly scrollWidth: number; + readonly scrollHeight: number; + + readonly viewportStartLineNumber: number; + readonly viewportEndLineNumber: number; + + readonly scrollTop: number; + readonly scrollLeft: number; + + readonly viewportWidth: number; + readonly viewportHeight: number; +} + +export class Minimap extends ViewPart implements IMinimapModel { + + private _selections: Selection[]; + private _minimapSelections: Selection[]; + + private _samplingRatio!: number; + private _minimapLines!: number[]; + private _isSampling = false;//true; + + public readonly tokensColorTracker: MinimapTokensColorTracker; + public options: MinimapOptions; - private _model: MinimapModel; private _actual: InnerMinimap; constructor(context: ViewContext) { super(context); - this._model = new MinimapModel(context); - this._actual = new InnerMinimap(context.configuration, context.theme, this._model); + this._selections = []; + this._minimapSelections = []; + + this._recreateLineMapping(); + + this.tokensColorTracker = MinimapTokensColorTracker.getInstance(); + this.options = new MinimapOptions(this._context.configuration, this._context.theme, this.tokensColorTracker, this._isSampling); + + this._actual = new InnerMinimap(context.theme, this); } public dispose(): void { @@ -479,84 +534,213 @@ export class Minimap extends ViewPart { return this._actual.getDomNode(); } + private _onOptionsMaybeChanged(): boolean { + const opts = new MinimapOptions(this._context.configuration, this._context.theme, this.tokensColorTracker, this._isSampling); + if (this.options.equals(opts)) { + return false; + } + this.options = opts; + this._actual.onDidChangeOptions(); + return true; + } + // ---- begin view event handlers public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { - return this._actual.onConfigurationChanged(e); + return this._onOptionsMaybeChanged(); } public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { - return this._actual.onCursorStateChanged(e); + this._selections = e.selections; + this._recomputeMinimapSelections(); + return this._actual.onSelectionChanged(); } public onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { - return this._actual.onDecorationsChanged(e); + return this._actual.onDecorationsChanged(); } public onFlushed(e: viewEvents.ViewFlushedEvent): boolean { - return this._actual.onFlushed(e); + return this._actual.onFlushed(); } public onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean { - return this._actual.onLinesChanged(e); + return this._actual.onLinesChanged(e); // TODO } public onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean { - return this._actual.onLinesDeleted(e); + return this._actual.onLinesDeleted(e); // TODO } public onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean { - return this._actual.onLinesInserted(e); + return this._actual.onLinesInserted(e); // TODO } public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { - return this._actual.onScrollChanged(e); + return this._actual.onScrollChanged(); } public onThemeChanged(e: viewEvents.ViewThemeChangedEvent): boolean { this._context.model.invalidateMinimapColorCache(); - this._actual.onThemeChanged(e); + this._actual.onThemeChanged(); + this._onOptionsMaybeChanged(); return true; } public onTokensChanged(e: viewEvents.ViewTokensChangedEvent): boolean { - return this._actual.onTokensChanged(e); + if (this._isSampling) { + let ranges: { fromLineNumber: number; toLineNumber: number; }[] = []; + for (const range of e.ranges) { + ranges.push({ + fromLineNumber: this._modelLineToMinimapLine(range.fromLineNumber), + toLineNumber: this._modelLineToMinimapLine(range.toLineNumber) + }); + } + return this._actual.onTokensChanged(ranges); + } else { + return this._actual.onTokensChanged(e.ranges); + } } public onTokensColorsChanged(e: viewEvents.ViewTokensColorsChangedEvent): boolean { - return this._actual.onTokensColorsChanged(e); + return this._actual.onTokensColorsChanged(); } public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { - return this._actual.onZonesChanged(e); + return this._actual.onZonesChanged(); } // --- end event handlers public prepareRender(ctx: RenderingContext): void { - this._actual.prepareRender(ctx); + // nothing to read } public render(ctx: RestrictedRenderingContext): void { - this._actual.render(ctx); + let viewportStartLineNumber = ctx.visibleRange.startLineNumber; + let viewportEndLineNumber = ctx.visibleRange.endLineNumber; + + if (this._isSampling) { + viewportStartLineNumber = this._modelLineToMinimapLine(viewportStartLineNumber); + viewportEndLineNumber = this._modelLineToMinimapLine(viewportEndLineNumber); + } + + const minimapCtx: IMinimapRenderingContext = { + samplingRatio: this._samplingRatio, + viewportContainsWhitespaceGaps: (ctx.viewportData.whitespaceViewportData.length > 0), + + scrollWidth: ctx.scrollWidth, + scrollHeight: ctx.scrollHeight, + + viewportStartLineNumber: viewportStartLineNumber, + viewportEndLineNumber: viewportEndLineNumber, + + scrollTop: ctx.scrollTop, + scrollLeft: ctx.scrollLeft, + + viewportWidth: ctx.viewportWidth, + viewportHeight: ctx.viewportHeight, + }; + this._actual.render(minimapCtx); } -} -class MinimapModel { + //#region IMinimapModel - private readonly _context: ViewContext; + private _recreateLineMapping(): void { + const options = this._context.configuration.options; + const layoutInfo = options.get(EditorOption.layoutInfo); + const pixelRatio = options.get(EditorOption.pixelRatio); - constructor(context: ViewContext) { - this._context = context; + const minimapLineCount = Math.round(pixelRatio * layoutInfo.height); + const modelLineCount = this._context.model.getLineCount(); + const ratio = modelLineCount / minimapLineCount; + + let result: number[] = []; + for (let i = 0; i < minimapLineCount; i++) { + const desiredLine = Math.min(modelLineCount, Math.round((i + 1) * ratio)); + result[i] = desiredLine; + } + + this._samplingRatio = ratio; + this._minimapLines = result; + } + + private _modelLineToMinimapLine(lineNumber: number): number { + return Math.min(this._minimapLines.length, Math.max(1, Math.round(lineNumber / this._samplingRatio))); + } + + private _recomputeMinimapSelections(): void { + this._minimapSelections = []; + for (const selection of this._selections) { + if (this._isSampling) { + const isMultiline = (selection.startLineNumber !== selection.endLineNumber); + + let minimapLineStart = this._modelLineToMinimapLine(selection.startLineNumber); + let minimapLineEnd = this._modelLineToMinimapLine(selection.endLineNumber); + if (isMultiline && minimapLineEnd === minimapLineStart) { + if (minimapLineEnd === this._minimapLines.length) { + if (minimapLineStart > 1) { + minimapLineStart--; + } + } else { + minimapLineEnd++; + } + } + + this._minimapSelections.push(new Selection(minimapLineStart, selection.startColumn, minimapLineEnd, selection.endColumn)); + } else { + this._minimapSelections.push(selection); + } + } } public getLineCount(): number { + if (this._isSampling) { + return this._minimapLines.length; + } return this._context.model.getLineCount(); } public getLineContent(lineNumber: number): string { + if (this._isSampling) { + return this._context.model.getLineContent(this._minimapLines[lineNumber - 1]); + } return this._context.model.getLineContent(lineNumber); } - public getLineMaxColumn(lineNumber: number): number { - return this._context.model.getLineMaxColumn(lineNumber); - } - - public getMinimapLinesRenderingData(startLineNumber: number, endLineNumber: number, needed: boolean[]): MinimapLinesRenderingData { + public getMinimapLinesRenderingData(startLineNumber: number, endLineNumber: number, needed: boolean[]): (ViewLineData | null)[] { + if (this._isSampling) { + let result: (ViewLineData | null)[] = []; + for (let lineIndex = 0, lineCount = endLineNumber - startLineNumber + 1; lineIndex < lineCount; lineIndex++) { + if (needed[lineIndex]) { + result[lineIndex] = this._context.model.getViewLineData(this._minimapLines[startLineNumber + lineIndex - 1]); + } else { + result[lineIndex] = null; + } + } + return result; + } return this._context.model.getMinimapLinesRenderingData(startLineNumber, endLineNumber, needed); } - public getDecorationsInViewport(visibleRange: Range): ViewModelDecoration[] { - return this._context.model.getDecorationsInViewport(visibleRange); + public getSelections(): Selection[] { + return this._minimapSelections; + } + + public getMinimapDecorationsInViewport(startLineNumber: number, endLineNumber: number): ViewModelDecoration[] { + let visibleRange: Range; + if (this._isSampling) { + const modelStartLineNumber = this._minimapLines[startLineNumber - 1]; + const modelEndLineNumber = this._minimapLines[endLineNumber - 1]; + visibleRange = new Range(modelStartLineNumber, 1, modelEndLineNumber, this._context.model.getLineMaxColumn(modelEndLineNumber)); + } else { + visibleRange = new Range(startLineNumber, 1, endLineNumber, this._context.model.getLineMaxColumn(endLineNumber)); + } + const decorations = this._context.model.getDecorationsInViewport(visibleRange); + + if (this._isSampling) { + let result: ViewModelDecoration[] = []; + for (const decoration of decorations) { + if (!decoration.options.minimap) { + continue; + } + const range = decoration.range; + const minimapStartLineNumber = this._modelLineToMinimapLine(range.startLineNumber); + const minimapEndLineNumber = this._modelLineToMinimapLine(range.endLineNumber); + result.push(new ViewModelDecoration(new Range(minimapStartLineNumber, range.startColumn, minimapEndLineNumber, range.endColumn), decoration.options)); + } + return result; + } + return decorations; } public getOptions(): TextModelResolvedOptions { @@ -564,6 +748,9 @@ class MinimapModel { } public revealLineNumber(lineNumber: number): void { + if (this._isSampling) { + lineNumber = this._minimapLines[lineNumber - 1]; + } this._context.privateViewEventBus.emit(new viewEvents.ViewRevealRangeRequestEvent( 'mouse', new Range(lineNumber, 1, lineNumber, 1), @@ -578,15 +765,15 @@ class MinimapModel { scrollTop: scrollTop }); } + + //#endregion } class InnerMinimap extends Disposable { - private readonly _configuration: IConfiguration; private readonly _theme: EditorTheme; - private readonly _model: MinimapModel; + private readonly _model: IMinimapModel; - private readonly _tokensColorTracker: MinimapTokensColorTracker; private readonly _domNode: FastDomNode; private readonly _shadow: FastDomNode; private readonly _canvas: FastDomNode; @@ -601,27 +788,21 @@ class InnerMinimap extends Disposable { private readonly _sliderTouchMoveListener: IDisposable; private readonly _sliderTouchEndListener: IDisposable; - private _options: MinimapOptions; private _lastRenderData: RenderData | null; - private _selections: Selection[] = []; private _selectionColor: Color | undefined; private _renderDecorations: boolean = false; private _gestureInProgress: boolean = false; private _buffers: MinimapBuffers | null; constructor( - configuration: IConfiguration, theme: EditorTheme, - model: MinimapModel + model: IMinimapModel ) { super(); - this._configuration = configuration; this._theme = theme; this._model = model; - this._tokensColorTracker = MinimapTokensColorTracker.getInstance(); - this._options = new MinimapOptions(this._configuration, this._theme, this._tokensColorTracker); this._lastRenderData = null; this._buffers = null; this._selectionColor = this._theme.getColor(minimapSelection); @@ -665,15 +846,15 @@ class InnerMinimap extends Disposable { this._mouseDownListener = dom.addStandardDisposableListener(this._domNode.domNode, 'mousedown', (e) => { e.preventDefault(); - const renderMinimap = this._options.renderMinimap; + const renderMinimap = this._model.options.renderMinimap; if (renderMinimap === RenderMinimap.None) { return; } if (!this._lastRenderData) { return; } - const minimapLineHeight = getMinimapLineHeight(renderMinimap, this._options.fontScale); - const internalOffsetY = this._options.pixelRatio * e.browserEvent.offsetY; + const minimapLineHeight = this._model.options.minimapLineHeight; + const internalOffsetY = this._model.options.pixelRatio * e.browserEvent.offsetY; const lineIndex = Math.floor(internalOffsetY / minimapLineHeight); let lineNumber = lineIndex + this._lastRenderData.renderedLayout.startLineNumber; @@ -762,7 +943,7 @@ class InnerMinimap extends Disposable { } private _getMinimapDomNodeClassName(): string { - if (this._options.showSlider === 'always') { + if (this._model.options.showSlider === 'always') { return 'minimap slider-always'; } return 'minimap slider-mouseover'; @@ -773,66 +954,55 @@ class InnerMinimap extends Disposable { } private _applyLayout(): void { - this._domNode.setLeft(this._options.minimapLeft); - this._domNode.setWidth(this._options.minimapWidth); - this._domNode.setHeight(this._options.minimapHeight); - this._shadow.setHeight(this._options.minimapHeight); + this._domNode.setLeft(this._model.options.minimapLeft); + this._domNode.setWidth(this._model.options.minimapWidth); + this._domNode.setHeight(this._model.options.minimapHeight); + this._shadow.setHeight(this._model.options.minimapHeight); - this._canvas.setWidth(this._options.canvasOuterWidth); - this._canvas.setHeight(this._options.canvasOuterHeight); - this._canvas.domNode.width = this._options.canvasInnerWidth; - this._canvas.domNode.height = this._options.canvasInnerHeight; + this._canvas.setWidth(this._model.options.canvasOuterWidth); + this._canvas.setHeight(this._model.options.canvasOuterHeight); + this._canvas.domNode.width = this._model.options.canvasInnerWidth; + this._canvas.domNode.height = this._model.options.canvasInnerHeight; - this._decorationsCanvas.setWidth(this._options.canvasOuterWidth); - this._decorationsCanvas.setHeight(this._options.canvasOuterHeight); - this._decorationsCanvas.domNode.width = this._options.canvasInnerWidth; - this._decorationsCanvas.domNode.height = this._options.canvasInnerHeight; + this._decorationsCanvas.setWidth(this._model.options.canvasOuterWidth); + this._decorationsCanvas.setHeight(this._model.options.canvasOuterHeight); + this._decorationsCanvas.domNode.width = this._model.options.canvasInnerWidth; + this._decorationsCanvas.domNode.height = this._model.options.canvasInnerHeight; - this._slider.setWidth(this._options.minimapWidth); + this._slider.setWidth(this._model.options.minimapWidth); } private _getBuffer(): ImageData | null { if (!this._buffers) { - if (this._options.canvasInnerWidth > 0 && this._options.canvasInnerHeight > 0) { + if (this._model.options.canvasInnerWidth > 0 && this._model.options.canvasInnerHeight > 0) { this._buffers = new MinimapBuffers( this._canvas.domNode.getContext('2d')!, - this._options.canvasInnerWidth, - this._options.canvasInnerHeight, - this._options.backgroundColor + this._model.options.canvasInnerWidth, + this._model.options.canvasInnerHeight, + this._model.options.backgroundColor ); } } return this._buffers ? this._buffers.getBuffer() : null; } - private _onOptionsMaybeChanged(): boolean { - const opts = new MinimapOptions(this._configuration, this._theme, this._tokensColorTracker); - if (this._options.equals(opts)) { - return false; - } - this._options = opts; + // ---- begin view event handlers + + public onDidChangeOptions(): void { this._lastRenderData = null; this._buffers = null; this._applyLayout(); this._domNode.setClassName(this._getMinimapDomNodeClassName()); - return true; } - - // ---- begin view event handlers - - public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { - return this._onOptionsMaybeChanged(); - } - public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { - this._selections = e.selections; + public onSelectionChanged(): boolean { this._renderDecorations = true; return true; } - public onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { + public onDecorationsChanged(): boolean { this._renderDecorations = true; return true; } - public onFlushed(e: viewEvents.ViewFlushedEvent): boolean { + public onFlushed(): boolean { this._lastRenderData = null; return true; } @@ -854,40 +1024,35 @@ class InnerMinimap extends Disposable { } return true; } - public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { + public onScrollChanged(): boolean { this._renderDecorations = true; return true; } - public onThemeChanged(e: viewEvents.ViewThemeChangedEvent): boolean { + public onThemeChanged(): boolean { this._selectionColor = this._theme.getColor(minimapSelection); this._renderDecorations = true; - this._onOptionsMaybeChanged(); return true; } - public onTokensChanged(e: viewEvents.ViewTokensChangedEvent): boolean { + public onTokensChanged(ranges: { fromLineNumber: number; toLineNumber: number; }[]): boolean { if (this._lastRenderData) { - return this._lastRenderData.onTokensChanged(e); + return this._lastRenderData.onTokensChanged(ranges); } return false; } - public onTokensColorsChanged(e: viewEvents.ViewTokensColorsChangedEvent): boolean { + public onTokensColorsChanged(): boolean { this._lastRenderData = null; this._buffers = null; return true; } - public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { + public onZonesChanged(): boolean { this._lastRenderData = null; return true; } // --- end event handlers - public prepareRender(ctx: RenderingContext): void { - // Nothing to read - } - - public render(renderingCtx: RestrictedRenderingContext): void { - const renderMinimap = this._options.renderMinimap; + public render(renderingCtx: IMinimapRenderingContext): void { + const renderMinimap = this._model.options.renderMinimap; if (renderMinimap === RenderMinimap.None) { this._shadow.setClassName('minimap-shadow-hidden'); this._sliderHorizontal.setWidth(0); @@ -901,11 +1066,12 @@ class InnerMinimap extends Disposable { } const layout = MinimapLayout.create( - this._options, - renderingCtx.visibleRange.startLineNumber, - renderingCtx.visibleRange.endLineNumber, + this._model.options, + renderingCtx.samplingRatio, + renderingCtx.viewportStartLineNumber, + renderingCtx.viewportEndLineNumber, renderingCtx.viewportHeight, - (renderingCtx.viewportData.whitespaceViewportData.length > 0), + renderingCtx.viewportContainsWhitespaceGaps, this._model.getLineCount(), renderingCtx.scrollTop, renderingCtx.scrollHeight, @@ -915,10 +1081,10 @@ class InnerMinimap extends Disposable { this._slider.setHeight(layout.sliderHeight); // Compute horizontal slider coordinates - const scrollLeftChars = renderingCtx.scrollLeft / this._options.typicalHalfwidthCharacterWidth; - const horizontalSliderLeft = Math.min(this._options.minimapWidth, Math.round(scrollLeftChars * getMinimapCharWidth(this._options.renderMinimap, this._options.fontScale) / this._options.pixelRatio)); + const scrollLeftChars = renderingCtx.scrollLeft / this._model.options.typicalHalfwidthCharacterWidth; + const horizontalSliderLeft = Math.min(this._model.options.minimapWidth, Math.round(scrollLeftChars * this._model.options.minimapCharWidth / this._model.options.pixelRatio)); this._sliderHorizontal.setLeft(horizontalSliderLeft); - this._sliderHorizontal.setWidth(this._options.minimapWidth - horizontalSliderLeft); + this._sliderHorizontal.setWidth(this._model.options.minimapWidth - horizontalSliderLeft); this._sliderHorizontal.setTop(0); this._sliderHorizontal.setHeight(layout.sliderHeight); @@ -929,19 +1095,20 @@ class InnerMinimap extends Disposable { private renderDecorations(layout: MinimapLayout) { if (this._renderDecorations) { this._renderDecorations = false; - const decorations = this._model.getDecorationsInViewport(new Range(layout.startLineNumber, 1, layout.endLineNumber, this._model.getLineMaxColumn(layout.endLineNumber))); + const selections = this._model.getSelections(); + const decorations = this._model.getMinimapDecorationsInViewport(layout.startLineNumber, layout.endLineNumber); - const { renderMinimap, canvasInnerWidth, canvasInnerHeight } = this._options; - const lineHeight = getMinimapLineHeight(renderMinimap, this._options.fontScale); - const characterWidth = getMinimapCharWidth(renderMinimap, this._options.fontScale); + const { canvasInnerWidth, canvasInnerHeight } = this._model.options; + const lineHeight = this._model.options.minimapLineHeight; + const characterWidth = this._model.options.minimapCharWidth; const tabSize = this._model.getOptions().tabSize; const canvasContext = this._decorationsCanvas.domNode.getContext('2d')!; canvasContext.clearRect(0, 0, canvasInnerWidth, canvasInnerHeight); const lineOffsetMap = new Map(); - for (let i = 0; i < this._selections.length; i++) { - const selection = this._selections[i]; + for (let i = 0; i < selections.length; i++) { + const selection = selections[i]; for (let line = selection.startLineNumber; line <= selection.endLineNumber; line++) { this.renderDecorationOnLine(canvasContext, lineOffsetMap, selection, this._selectionColor, layout, line, lineHeight, lineHeight, tabSize, characterWidth); @@ -988,7 +1155,7 @@ class InnerMinimap extends Disposable { const y = (lineNumber - layout.startLineNumber) * lineHeight; // Skip rendering the line if it's vertically outside our viewport - if (y + height < 0 || y > this._options.canvasInnerHeight) { + if (y + height < 0 || y > this._model.options.canvasInnerHeight) { return; } @@ -1041,11 +1208,11 @@ class InnerMinimap extends Disposable { } private renderLines(layout: MinimapLayout): RenderData | null { - const renderMinimap = this._options.renderMinimap; - const charRenderer = this._options.charRenderer(); + const renderMinimap = this._model.options.renderMinimap; + const charRenderer = this._model.options.charRenderer(); const startLineNumber = layout.startLineNumber; const endLineNumber = layout.endLineNumber; - const minimapLineHeight = getMinimapLineHeight(renderMinimap, this._options.fontScale); + const minimapLineHeight = this._model.options.minimapLineHeight; // Check if nothing changed w.r.t. lines from last frame if (this._lastRenderData && this._lastRenderData.linesEquals(layout)) { @@ -1073,9 +1240,9 @@ class InnerMinimap extends Disposable { // Fetch rendering info from view model for rest of lines that need rendering. const lineInfo = this._model.getMinimapLinesRenderingData(startLineNumber, endLineNumber, needed); - const tabSize = lineInfo.tabSize; - const background = this._options.backgroundColor; - const useLighterFont = this._tokensColorTracker.backgroundIsLight(); + const tabSize = this._model.getOptions().tabSize; + const background = this._model.options.backgroundColor; + const useLighterFont = this._model.tokensColorTracker.backgroundIsLight(); // Render the rest of lines let dy = 0; @@ -1087,12 +1254,13 @@ class InnerMinimap extends Disposable { background, useLighterFont, renderMinimap, - this._tokensColorTracker, + this._model.options.minimapCharWidth, + this._model.tokensColorTracker, charRenderer, dy, tabSize, - lineInfo.data[lineIndex]!, - this._options.fontScale + lineInfo[lineIndex]!, + this._model.options.fontScale ); } renderedLines[lineIndex] = new MinimapLine(dy); @@ -1212,6 +1380,7 @@ class InnerMinimap extends Disposable { backgroundColor: RGBA8, useLighterFont: boolean, renderMinimap: RenderMinimap, + charWidth: number, colorTracker: MinimapTokensColorTracker, minimapCharRenderer: MinimapCharRenderer, dy: number, @@ -1221,7 +1390,6 @@ class InnerMinimap extends Disposable { ): void { const content = lineData.content; const tokens = lineData.tokens; - const charWidth = getMinimapCharWidth(renderMinimap, fontScale); const maxDx = target.width - charWidth; let dx = MINIMAP_GUTTER_WIDTH; diff --git a/src/vs/editor/common/viewModel/splitLinesCollection.ts b/src/vs/editor/common/viewModel/splitLinesCollection.ts index 3a3eab70a1c2..07c22199f397 100644 --- a/src/vs/editor/common/viewModel/splitLinesCollection.ts +++ b/src/vs/editor/common/viewModel/splitLinesCollection.ts @@ -129,7 +129,7 @@ export interface IViewModelLinesCollection extends IDisposable { getViewLineMinColumn(viewLineNumber: number): number; getViewLineMaxColumn(viewLineNumber: number): number; getViewLineData(viewLineNumber: number): ViewLineData; - getViewLinesData(viewStartLineNumber: number, viewEndLineNumber: number, needed: boolean[]): Array; + getViewLinesData(viewStartLineNumber: number, viewEndLineNumber: number, needed: boolean[]): (ViewLineData | null)[]; getAllOverviewRulerDecorations(ownerId: number, filterOutValidation: boolean, theme: EditorTheme): IOverviewRulerDecorations; getDecorationsInRange(range: Range, ownerId: number, filterOutValidation: boolean): IModelDecoration[]; diff --git a/src/vs/editor/common/viewModel/viewModel.ts b/src/vs/editor/common/viewModel/viewModel.ts index 529f386a1354..82051bbeb9b2 100644 --- a/src/vs/editor/common/viewModel/viewModel.ts +++ b/src/vs/editor/common/viewModel/viewModel.ts @@ -113,7 +113,7 @@ export interface IViewModel { getDecorationsInViewport(visibleRange: Range): ViewModelDecoration[]; getViewLineRenderingData(visibleRange: Range, lineNumber: number): ViewLineRenderingData; getViewLineData(lineNumber: number): ViewLineData; - getMinimapLinesRenderingData(startLineNumber: number, endLineNumber: number, needed: boolean[]): MinimapLinesRenderingData; + getMinimapLinesRenderingData(startLineNumber: number, endLineNumber: number, needed: boolean[]): (ViewLineData | null)[]; getCompletelyVisibleViewRange(): Range; getCompletelyVisibleViewRangeAtScrollTop(scrollTop: number): Range; @@ -142,19 +142,6 @@ export interface IViewModel { getRichTextToCopy(modelRanges: Range[], emptySelectionClipboard: boolean): { html: string, mode: string } | null; } -export class MinimapLinesRenderingData { - public readonly tabSize: number; - public readonly data: Array; - - constructor( - tabSize: number, - data: Array - ) { - this.tabSize = tabSize; - this.data = data; - } -} - export class ViewLineData { _viewLineDataBrand: void; diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index ed3b33825357..ebd648563971 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -19,7 +19,7 @@ import { MinimapTokensColorTracker } from 'vs/editor/common/viewModel/minimapTok import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { ViewLayout } from 'vs/editor/common/viewLayout/viewLayout'; import { IViewModelLinesCollection, IdentityLinesCollection, SplitLinesCollection, ILineBreaksComputerFactory } from 'vs/editor/common/viewModel/splitLinesCollection'; -import { ICoordinatesConverter, IOverviewRulerDecorations, IViewModel, MinimapLinesRenderingData, ViewLineData, ViewLineRenderingData, ViewModelDecoration } from 'vs/editor/common/viewModel/viewModel'; +import { ICoordinatesConverter, IOverviewRulerDecorations, IViewModel, ViewLineData, ViewLineRenderingData, ViewModelDecoration } from 'vs/editor/common/viewModel/viewModel'; import { ViewModelDecorations } from 'vs/editor/common/viewModel/viewModelDecorations'; import { RunOnceScheduler } from 'vs/base/common/async'; import * as platform from 'vs/base/common/platform'; @@ -587,12 +587,8 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel return this.lines.getViewLineData(lineNumber); } - public getMinimapLinesRenderingData(startLineNumber: number, endLineNumber: number, needed: boolean[]): MinimapLinesRenderingData { - let result = this.lines.getViewLinesData(startLineNumber, endLineNumber, needed); - return new MinimapLinesRenderingData( - this.getTabSize(), - result - ); + public getMinimapLinesRenderingData(startLineNumber: number, endLineNumber: number, needed: boolean[]): (ViewLineData | null)[] { + return this.lines.getViewLinesData(startLineNumber, endLineNumber, needed); } public getAllOverviewRulerDecorations(theme: EditorTheme): IOverviewRulerDecorations { From 47e69381e368760bd152530b891293778d2ada61 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 12 Feb 2020 15:44:37 +0100 Subject: [PATCH 004/109] wip: empty view api --- .../common/documentationContribution.ts | 53 ++++++++++--------- .../common/documentationExtensionPoint.ts | 35 ++++++++++++ 2 files changed, 63 insertions(+), 25 deletions(-) diff --git a/src/vs/workbench/contrib/documentation/common/documentationContribution.ts b/src/vs/workbench/contrib/documentation/common/documentationContribution.ts index 57547e37c1df..97a97463f013 100644 --- a/src/vs/workbench/contrib/documentation/common/documentationContribution.ts +++ b/src/vs/workbench/contrib/documentation/common/documentationContribution.ts @@ -15,18 +15,16 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IExtensionPoint } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { DocumentationExtensionPoint } from './documentationExtensionPoint'; +interface ICodeActionContribution { + title: string; + when: ContextKeyExpr; + command: string; +} + export class DocumentationContribution extends Disposable implements IWorkbenchContribution, modes.CodeActionProvider { - private contributions: { - title: string; - when: ContextKeyExpr; - command: string; - }[] = []; - - private readonly emptyCodeActionsList = { - actions: [], - dispose: () => { } - }; + private codeActionContributions: ICodeActionContribution[] = []; + private readonly emptyCodeActionsList = { actions: [], dispose: () => { } }; constructor( extensionPoint: IExtensionPoint, @@ -37,24 +35,29 @@ export class DocumentationContribution extends Disposable implements IWorkbenchC this._register(modes.CodeActionProviderRegistry.register('*', this)); extensionPoint.setHandler(points => { - this.contributions = []; + this.codeActionContributions = []; for (const documentation of points) { - if (!documentation.value.refactoring) { - continue; + if (documentation.value.refactoring) { + for (const contribution of documentation.value.refactoring) { + const precondition = ContextKeyExpr.deserialize(contribution.when); + if (!precondition) { + continue; + } + + this.codeActionContributions.push({ + title: contribution.title, + when: precondition, + command: contribution.command + }); + } } - for (const contribution of documentation.value.refactoring) { - const precondition = ContextKeyExpr.deserialize(contribution.when); - if (!precondition) { - continue; + if (documentation.value.view) { + for (const contribution of documentation.value.view) { + const precondition = ContextKeyExpr.deserialize(contribution.when); + + // TODO } - - this.contributions.push({ - title: contribution.title, - when: precondition, - command: contribution.command - }); - } } }); @@ -71,7 +74,7 @@ export class DocumentationContribution extends Disposable implements IWorkbenchC } } - return this.contributions + return this.codeActionContributions .filter(contribution => this.contextKeyService.contextMatchesRules(contribution.when)) .map(contribution => { return { diff --git a/src/vs/workbench/contrib/documentation/common/documentationExtensionPoint.ts b/src/vs/workbench/contrib/documentation/common/documentationExtensionPoint.ts index bb848f8d64d1..d2099c4a8bc4 100644 --- a/src/vs/workbench/contrib/documentation/common/documentationExtensionPoint.ts +++ b/src/vs/workbench/contrib/documentation/common/documentationExtensionPoint.ts @@ -11,6 +11,8 @@ export enum DocumentationExtensionPointFields { when = 'when', title = 'title', command = 'command', + view = 'view', + contents = 'contents', } export interface RefactoringDocumentationExtensionPoint { @@ -19,8 +21,15 @@ export interface RefactoringDocumentationExtensionPoint { readonly [DocumentationExtensionPointFields.command]: string; } +export interface ViewDocumentationExtensionPoint { + readonly [DocumentationExtensionPointFields.view]: string; + readonly [DocumentationExtensionPointFields.contents]: string; + readonly [DocumentationExtensionPointFields.when]: string; +} + export interface DocumentationExtensionPoint { readonly refactoring?: readonly RefactoringDocumentationExtensionPoint[]; + readonly view?: readonly ViewDocumentationExtensionPoint[]; } const documentationExtensionPointSchema = Object.freeze({ @@ -53,6 +62,32 @@ const documentationExtensionPointSchema = Object.freeze Date: Wed, 12 Feb 2020 12:18:52 -0800 Subject: [PATCH 005/109] Fix suspected bug, replacing string with itself The original code did a string.replace('|', '\|'), which is a no-op. I think it was meant to escape pipes, in which case, you need another slash in there. I did not test this, and have no idea of the context for this code. --- src/vs/platform/markers/common/markers.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/platform/markers/common/markers.ts b/src/vs/platform/markers/common/markers.ts index 0d852a63de83..e89a52153d2f 100644 --- a/src/vs/platform/markers/common/markers.ts +++ b/src/vs/platform/markers/common/markers.ts @@ -135,15 +135,15 @@ export namespace IMarkerData { export function makeKeyOptionalMessage(markerData: IMarkerData, useMessage: boolean): string { let result: string[] = [emptyString]; if (markerData.source) { - result.push(markerData.source.replace('¦', '\¦')); + result.push(markerData.source.replace('¦', '\\¦')); } else { result.push(emptyString); } if (markerData.code) { if (typeof markerData.code === 'string') { - result.push(markerData.code.replace('¦', '\¦')); + result.push(markerData.code.replace('¦', '\\¦')); } else { - result.push(markerData.code.value.replace('¦', '\¦')); + result.push(markerData.code.value.replace('¦', '\\¦')); } } else { result.push(emptyString); @@ -157,7 +157,7 @@ export namespace IMarkerData { // Modifed to not include the message as part of the marker key to work around // https://github.com/microsoft/vscode/issues/77475 if (markerData.message && useMessage) { - result.push(markerData.message.replace('¦', '\¦')); + result.push(markerData.message.replace('¦', '\\¦')); } else { result.push(emptyString); } From 60294bf9d95f5a13bcec96c7c6584278a6f5896f Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 13 Feb 2020 12:03:12 +0100 Subject: [PATCH 006/109] Leave a gap at the end when scrolling beyond last line --- src/vs/editor/browser/viewParts/minimap/minimap.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index f8b400f81613..54cd002d9b1e 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -639,9 +639,13 @@ export class Minimap extends ViewPart implements IMinimapModel { const options = this._context.configuration.options; const layoutInfo = options.get(EditorOption.layoutInfo); const pixelRatio = options.get(EditorOption.pixelRatio); - - const minimapLineCount = Math.round(pixelRatio * layoutInfo.height); + const lineHeight = options.get(EditorOption.lineHeight); + const scrollBeyondLastLine = options.get(EditorOption.scrollBeyondLastLine); const modelLineCount = this._context.model.getLineCount(); + + const extraLinesBeyondLastLine = scrollBeyondLastLine ? (layoutInfo.height / lineHeight - 1) : 0; + const desiredRatio = (modelLineCount + extraLinesBeyondLastLine) / (pixelRatio * layoutInfo.height); + const minimapLineCount = Math.floor(modelLineCount / desiredRatio); const ratio = modelLineCount / minimapLineCount; let result: number[] = []; From 59171e630741d866b39f7b1fa5540ade3c196637 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 13 Feb 2020 12:24:11 +0100 Subject: [PATCH 007/109] empty views: link API to registry to view pane container --- .../browser/parts/views/viewPaneContainer.ts | 83 ++++++++++++++++--- src/vs/workbench/common/views.ts | 6 ++ .../common/documentationContribution.ts | 34 ++++++-- 3 files changed, 104 insertions(+), 19 deletions(-) diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index f50f32cc0ae2..1ed47c4dfbc8 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -25,7 +25,7 @@ import { PaneView, IPaneViewOptions, IPaneOptions, Pane, DefaultPaneDndControlle import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; -import { Extensions as ViewContainerExtensions, IView, FocusedViewContext, IViewContainersRegistry, IViewDescriptor, ViewContainer, IViewDescriptorService, ViewContainerLocation, IViewPaneContainer, IViewsRegistry } from 'vs/workbench/common/views'; +import { Extensions as ViewContainerExtensions, IView, FocusedViewContext, IViewContainersRegistry, IViewDescriptor, ViewContainer, IViewDescriptorService, ViewContainerLocation, IViewPaneContainer, IViewsRegistry, IViewContentDescriptor } from 'vs/workbench/common/views'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { assertIsDefined } from 'vs/base/common/types'; @@ -60,6 +60,67 @@ export interface IViewPaneOptions extends IPaneOptions { const viewsRegistry = Registry.as(ViewContainerExtensions.ViewsRegistry); +interface IEmptyViewItem { + readonly view: IViewContentDescriptor; + visible: boolean; +} + +class EmptyViewsController { + + private _onDidChange = new Emitter(); + readonly onDidChange = this._onDidChange.event; + + private viewItems: IEmptyViewItem[] = []; + get views(): IViewContentDescriptor[] { return this.viewItems.map(v => v.view); } + + private disposables = new DisposableStore(); + + constructor( + private id: string, + @IContextKeyService private contextKeyService: IContextKeyService, + ) { + contextKeyService.onDidChangeContext(this.onDidChangeContext, this, this.disposables); + Event.filter(viewsRegistry.onDidChangeEmptyViewContent, id => id === this.id)(this.onDidChangeEmptyViewContent, this, this.disposables); + this.onDidChangeEmptyViewContent(); + } + + private onDidChangeEmptyViewContent(): void { + this.viewItems = viewsRegistry.getEmptyViewContent(this.id).map(view => ({ + view, + visible: view.when ? this.contextKeyService.contextMatchesRules(view.when) : true + })); + + this._onDidChange.fire(); + } + + private onDidChangeContext(): void { + let didChange = false; + + for (const item of this.viewItems) { + if (!item.view.when) { + continue; + } + + const visible = this.contextKeyService.contextMatchesRules(item.view.when); + + if (item.visible === visible) { + continue; + } + + item.visible = visible; + didChange = true; + } + + if (didChange) { + this._onDidChange.fire(); + } + } + + dispose(): void { + this.disposables.dispose(); + } +} + export abstract class ViewPane extends Pane implements IView { private static readonly AlwaysShowActionsConfig = 'workbench.view.alwaysShowHeaderActions'; @@ -97,6 +158,7 @@ export abstract class ViewPane extends Pane implements IView { private bodyContainer!: HTMLElement; private emptyViewContainer!: HTMLElement; private emptyViewDisposable: IDisposable = Disposable.None; + private emptyViewsController: EmptyViewsController; constructor( options: IViewPaneOptions, @@ -119,6 +181,8 @@ export abstract class ViewPane extends Pane implements IView { this.menuActions = this._register(instantiationService.createInstance(ViewMenuActions, this.id, options.titleMenuId || MenuId.ViewTitle, MenuId.ViewTitleContext)); this._register(this.menuActions.onDidChangeTitle(() => this.updateActions())); + + this.emptyViewsController = new EmptyViewsController(this.id, contextKeyService); } setVisible(visible: boolean): void { @@ -208,16 +272,9 @@ export abstract class ViewPane extends Pane implements IView { this.bodyContainer = container; this.emptyViewContainer = append(container, $('.empty-view', { tabIndex: 0 })); - // we should update our empty state whenever - const onEmptyViewContentChange = Event.any( - // the registry changes - Event.map(Event.filter(viewsRegistry.onDidChangeEmptyViewContent, id => id === this.id), () => this.isEmpty()), - // or the view's empty state changes - Event.latch(Event.map(this.onDidChangeEmptyState, () => this.isEmpty())) - ); - - this._register(onEmptyViewContentChange(this.updateEmptyState, this)); - this.updateEmptyState(this.isEmpty()); + const onEmptyViewContentChange = Event.any(this.emptyViewsController.onDidChange, this.onDidChangeEmptyState); + this._register(onEmptyViewContentChange(this.updateEmptyContents, this)); + this.updateEmptyContents(); } protected getProgressLocation(): string { @@ -286,10 +343,10 @@ export abstract class ViewPane extends Pane implements IView { // Subclasses to implement for saving state } - private updateEmptyState(isEmpty: boolean): void { + private updateEmptyContents(): void { this.emptyViewDisposable.dispose(); - if (!isEmpty) { + if (!this.isEmpty()) { removeClass(this.bodyContainer, 'empty'); this.emptyViewContainer.innerHTML = ''; return; diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 0a6073ca70b6..0b1f83c1a93b 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -213,6 +213,7 @@ export interface IViewDescriptorCollection extends IDisposable { export interface IViewContentDescriptor { readonly content: string; + readonly when?: ContextKeyExpr; } export interface IViewsRegistry { @@ -240,6 +241,10 @@ export interface IViewsRegistry { getEmptyViewContent(id: string): IViewContentDescriptor[]; } +function compareViewContentDescriptors(a: IViewContentDescriptor, b: IViewContentDescriptor): number { + return a.content < b.content ? -1 : 1; +} + class ViewsRegistry extends Disposable implements IViewsRegistry { private readonly _onViewsRegistered: Emitter<{ views: IViewDescriptor[], viewContainer: ViewContainer }> = this._register(new Emitter<{ views: IViewDescriptor[], viewContainer: ViewContainer }>()); @@ -318,6 +323,7 @@ class ViewsRegistry extends Disposable implements IViewsRegistry { getEmptyViewContent(id: string): IViewContentDescriptor[] { const result: IViewContentDescriptor[] = []; + result.sort(compareViewContentDescriptors); this._emptyViewContents.forEach(id, descriptor => result.push(descriptor)); return result; } diff --git a/src/vs/workbench/contrib/documentation/common/documentationContribution.ts b/src/vs/workbench/contrib/documentation/common/documentationContribution.ts index 97a97463f013..eb54ebe45f82 100644 --- a/src/vs/workbench/contrib/documentation/common/documentationContribution.ts +++ b/src/vs/workbench/contrib/documentation/common/documentationContribution.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { CancellationToken } from 'vs/base/common/cancellation'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { ITextModel } from 'vs/editor/common/model'; @@ -13,7 +13,9 @@ import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IExtensionPoint } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { DocumentationExtensionPoint } from './documentationExtensionPoint'; +import { DocumentationExtensionPoint, ViewDocumentationExtensionPoint } from './documentationExtensionPoint'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { Extensions as ViewContainerExtensions, IViewsRegistry } from 'vs/workbench/common/views'; interface ICodeActionContribution { title: string; @@ -21,11 +23,15 @@ interface ICodeActionContribution { command: string; } +const viewsRegistry = Registry.as(ViewContainerExtensions.ViewsRegistry); + export class DocumentationContribution extends Disposable implements IWorkbenchContribution, modes.CodeActionProvider { private codeActionContributions: ICodeActionContribution[] = []; private readonly emptyCodeActionsList = { actions: [], dispose: () => { } }; + private emptyViewContents = new Map(); + constructor( extensionPoint: IExtensionPoint, @IContextKeyService private readonly contextKeyService: IContextKeyService, @@ -34,8 +40,7 @@ export class DocumentationContribution extends Disposable implements IWorkbenchC this._register(modes.CodeActionProviderRegistry.register('*', this)); - extensionPoint.setHandler(points => { - this.codeActionContributions = []; + extensionPoint.setHandler((points, { added, removed }) => { for (const documentation of points) { if (documentation.value.refactoring) { for (const contribution of documentation.value.refactoring) { @@ -51,12 +56,29 @@ export class DocumentationContribution extends Disposable implements IWorkbenchC }); } } + } + for (const documentation of removed) { if (documentation.value.view) { for (const contribution of documentation.value.view) { - const precondition = ContextKeyExpr.deserialize(contribution.when); + const disposable = this.emptyViewContents.get(contribution); - // TODO + if (disposable) { + disposable.dispose(); + } + } + } + } + + for (const documentation of added) { + if (documentation.value.view) { + for (const contribution of documentation.value.view) { + const disposable = viewsRegistry.registerEmptyViewContent(contribution.view, { + content: contribution.contents, + when: ContextKeyExpr.deserialize(contribution.when) + }); + + this.emptyViewContents.set(contribution, disposable); } } } From 4e268350e49eab90ec1e8c58dde3d00eae989129 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 13 Feb 2020 12:45:43 +0100 Subject: [PATCH 008/109] Adjust sampled lines with model events --- .../browser/viewParts/minimap/minimap.ts | 83 +++++++++++++++---- 1 file changed, 68 insertions(+), 15 deletions(-) diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index 54cd002d9b1e..2b35d39d44a9 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -399,14 +399,14 @@ class RenderData { }; } - public onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean { - return this._renderedLines.onLinesChanged(e.fromLineNumber, e.toLineNumber); + public onLinesChanged(changeFromLineNumber: number, changeToLineNumber: number): boolean { + return this._renderedLines.onLinesChanged(changeFromLineNumber, changeToLineNumber); } - public onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): void { - this._renderedLines.onLinesDeleted(e.fromLineNumber, e.toLineNumber); + public onLinesDeleted(deleteFromLineNumber: number, deleteToLineNumber: number): void { + this._renderedLines.onLinesDeleted(deleteFromLineNumber, deleteToLineNumber); } - public onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): void { - this._renderedLines.onLinesInserted(e.fromLineNumber, e.toLineNumber); + public onLinesInserted(insertFromLineNumber: number, insertToLineNumber: number): void { + this._renderedLines.onLinesInserted(insertFromLineNumber, insertToLineNumber); } public onTokensChanged(ranges: { fromLineNumber: number; toLineNumber: number; }[]): boolean { return this._renderedLines.onTokensChanged(ranges); @@ -561,13 +561,61 @@ export class Minimap extends ViewPart implements IMinimapModel { return this._actual.onFlushed(); } public onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean { - return this._actual.onLinesChanged(e); // TODO + if (this._isSampling) { + let fromLineIndex = this._modelLineToMinimapLine(e.fromLineNumber) - 1; + while (fromLineIndex > 0 && this._minimapLines[fromLineIndex - 1] >= e.fromLineNumber) { + fromLineIndex--; + } + let toLineIndex = this._modelLineToMinimapLine(e.toLineNumber) - 1; + while (toLineIndex + 1 < this._minimapLines.length && this._minimapLines[toLineIndex + 1] <= e.toLineNumber) { + toLineIndex++; + } + return this._actual.onLinesChanged(fromLineIndex + 1, toLineIndex + 1); + } else { + return this._actual.onLinesChanged(e.fromLineNumber, e.toLineNumber); + } } public onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean { - return this._actual.onLinesDeleted(e); // TODO + if (this._isSampling) { + // have the mapping be sticky + const deletedLineCount = e.toLineNumber - e.fromLineNumber + 1; + let changeStartIndex = this._minimapLines.length; + let changeEndIndex = 0; + for (let i = this._minimapLines.length - 1; i >= 0; i--) { + if (this._minimapLines[i] < e.fromLineNumber) { + break; + } + if (this._minimapLines[i] <= e.toLineNumber) { + // this line got deleted => move to previous available + this._minimapLines[i] = Math.max(1, e.fromLineNumber - 1); + changeStartIndex = Math.min(changeStartIndex, i); + changeEndIndex = Math.max(changeEndIndex, i); + } else { + this._minimapLines[i] -= deletedLineCount; + } + } + if (changeStartIndex <= changeEndIndex) { + this._actual.onLinesChanged(changeStartIndex + 1, changeEndIndex + 1); + } + return this._checkMapping(); + } else { + return this._actual.onLinesDeleted(e.fromLineNumber, e.toLineNumber); + } } public onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean { - return this._actual.onLinesInserted(e); // TODO + if (this._isSampling) { + // have the mapping be sticky + const insertedLineCount = e.toLineNumber - e.fromLineNumber + 1; + for (let i = this._minimapLines.length - 1; i >= 0; i--) { + if (this._minimapLines[i] < e.fromLineNumber) { + break; + } + this._minimapLines[i] += insertedLineCount; + } + return this._checkMapping(); + } else { + return this._actual.onLinesInserted(e.fromLineNumber, e.toLineNumber); + } } public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { return this._actual.onScrollChanged(); @@ -635,6 +683,11 @@ export class Minimap extends ViewPart implements IMinimapModel { //#region IMinimapModel + private _checkMapping(): boolean { + // console.log(`TODO: I SHOULD ADJUST THE MAPPING!!!`, this._minimapLines); + return false; + } + private _recreateLineMapping(): void { const options = this._context.configuration.options; const layoutInfo = options.get(EditorOption.layoutInfo); @@ -1010,21 +1063,21 @@ class InnerMinimap extends Disposable { this._lastRenderData = null; return true; } - public onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean { + public onLinesChanged(changeFromLineNumber: number, changeToLineNumber: number): boolean { if (this._lastRenderData) { - return this._lastRenderData.onLinesChanged(e); + return this._lastRenderData.onLinesChanged(changeFromLineNumber, changeToLineNumber); } return false; } - public onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean { + public onLinesDeleted(deleteFromLineNumber: number, deleteToLineNumber: number): boolean { if (this._lastRenderData) { - this._lastRenderData.onLinesDeleted(e); + this._lastRenderData.onLinesDeleted(deleteFromLineNumber, deleteToLineNumber); } return true; } - public onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean { + public onLinesInserted(insertFromLineNumber: number, insertToLineNumber: number): boolean { if (this._lastRenderData) { - this._lastRenderData.onLinesInserted(e); + this._lastRenderData.onLinesInserted(insertFromLineNumber, insertToLineNumber); } return true; } From 3f315eda04964ebde47398bb13d8c2ef423c705a Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 13 Feb 2020 12:59:58 +0100 Subject: [PATCH 009/109] Deliver events only if sampled lines are affected --- .../browser/viewParts/minimap/minimap.ts | 49 ++++++++++++++----- 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index 2b35d39d44a9..a1613f550bc8 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -562,15 +562,12 @@ export class Minimap extends ViewPart implements IMinimapModel { } public onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean { if (this._isSampling) { - let fromLineIndex = this._modelLineToMinimapLine(e.fromLineNumber) - 1; - while (fromLineIndex > 0 && this._minimapLines[fromLineIndex - 1] >= e.fromLineNumber) { - fromLineIndex--; + const minimapLineRange = this._modelLineRangeToMinimapLineRange(e.fromLineNumber, e.toLineNumber); + if (minimapLineRange) { + return this._actual.onLinesChanged(minimapLineRange[0], minimapLineRange[1]); + } else { + return false; } - let toLineIndex = this._modelLineToMinimapLine(e.toLineNumber) - 1; - while (toLineIndex + 1 < this._minimapLines.length && this._minimapLines[toLineIndex + 1] <= e.toLineNumber) { - toLineIndex++; - } - return this._actual.onLinesChanged(fromLineIndex + 1, toLineIndex + 1); } else { return this._actual.onLinesChanged(e.fromLineNumber, e.toLineNumber); } @@ -630,12 +627,16 @@ export class Minimap extends ViewPart implements IMinimapModel { if (this._isSampling) { let ranges: { fromLineNumber: number; toLineNumber: number; }[] = []; for (const range of e.ranges) { - ranges.push({ - fromLineNumber: this._modelLineToMinimapLine(range.fromLineNumber), - toLineNumber: this._modelLineToMinimapLine(range.toLineNumber) - }); + const minimapLineRange = this._modelLineRangeToMinimapLineRange(range.fromLineNumber, range.toLineNumber); + if (minimapLineRange) { + ranges.push({ fromLineNumber: minimapLineRange[0], toLineNumber: minimapLineRange[1] }); + } + } + if (ranges.length) { + return this._actual.onTokensChanged(ranges); + } else { + return false; } - return this._actual.onTokensChanged(ranges); } else { return this._actual.onTokensChanged(e.ranges); } @@ -715,6 +716,28 @@ export class Minimap extends ViewPart implements IMinimapModel { return Math.min(this._minimapLines.length, Math.max(1, Math.round(lineNumber / this._samplingRatio))); } + /** + * Will return null if the model line ranges are not intersecting with a sampled model line. + */ + private _modelLineRangeToMinimapLineRange(fromLineNumber: number, toLineNumber: number): [number, number] | null { + let fromLineIndex = this._modelLineToMinimapLine(fromLineNumber) - 1; + while (fromLineIndex > 0 && this._minimapLines[fromLineIndex - 1] >= fromLineNumber) { + fromLineIndex--; + } + let toLineIndex = this._modelLineToMinimapLine(toLineNumber) - 1; + while (toLineIndex + 1 < this._minimapLines.length && this._minimapLines[toLineIndex + 1] <= toLineNumber) { + toLineIndex++; + } + if (fromLineIndex === toLineIndex) { + const sampledLineNumber = this._minimapLines[fromLineIndex]; + if (sampledLineNumber < fromLineNumber || sampledLineNumber > toLineNumber) { + // This line is not part of the sampled lines ==> nothing to do + return null; + } + } + return [fromLineIndex + 1, toLineIndex + 1]; + } + private _recomputeMinimapSelections(): void { this._minimapSelections = []; for (const selection of this._selections) { From b4ca4e42fffc17d112fb0371f190dd4c46b1100e Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 13 Feb 2020 13:05:45 +0100 Subject: [PATCH 010/109] Reduce diff --- .../editor/browser/viewParts/minimap/minimap.ts | 2 +- .../common/viewModel/splitLinesCollection.ts | 2 +- src/vs/editor/common/viewModel/viewModel.ts | 15 ++++++++++++++- src/vs/editor/common/viewModel/viewModelImpl.ts | 10 +++++++--- 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index a1613f550bc8..8e1846c86412 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -789,7 +789,7 @@ export class Minimap extends ViewPart implements IMinimapModel { } return result; } - return this._context.model.getMinimapLinesRenderingData(startLineNumber, endLineNumber, needed); + return this._context.model.getMinimapLinesRenderingData(startLineNumber, endLineNumber, needed).data; } public getSelections(): Selection[] { diff --git a/src/vs/editor/common/viewModel/splitLinesCollection.ts b/src/vs/editor/common/viewModel/splitLinesCollection.ts index 07c22199f397..3a3eab70a1c2 100644 --- a/src/vs/editor/common/viewModel/splitLinesCollection.ts +++ b/src/vs/editor/common/viewModel/splitLinesCollection.ts @@ -129,7 +129,7 @@ export interface IViewModelLinesCollection extends IDisposable { getViewLineMinColumn(viewLineNumber: number): number; getViewLineMaxColumn(viewLineNumber: number): number; getViewLineData(viewLineNumber: number): ViewLineData; - getViewLinesData(viewStartLineNumber: number, viewEndLineNumber: number, needed: boolean[]): (ViewLineData | null)[]; + getViewLinesData(viewStartLineNumber: number, viewEndLineNumber: number, needed: boolean[]): Array; getAllOverviewRulerDecorations(ownerId: number, filterOutValidation: boolean, theme: EditorTheme): IOverviewRulerDecorations; getDecorationsInRange(range: Range, ownerId: number, filterOutValidation: boolean): IModelDecoration[]; diff --git a/src/vs/editor/common/viewModel/viewModel.ts b/src/vs/editor/common/viewModel/viewModel.ts index 82051bbeb9b2..529f386a1354 100644 --- a/src/vs/editor/common/viewModel/viewModel.ts +++ b/src/vs/editor/common/viewModel/viewModel.ts @@ -113,7 +113,7 @@ export interface IViewModel { getDecorationsInViewport(visibleRange: Range): ViewModelDecoration[]; getViewLineRenderingData(visibleRange: Range, lineNumber: number): ViewLineRenderingData; getViewLineData(lineNumber: number): ViewLineData; - getMinimapLinesRenderingData(startLineNumber: number, endLineNumber: number, needed: boolean[]): (ViewLineData | null)[]; + getMinimapLinesRenderingData(startLineNumber: number, endLineNumber: number, needed: boolean[]): MinimapLinesRenderingData; getCompletelyVisibleViewRange(): Range; getCompletelyVisibleViewRangeAtScrollTop(scrollTop: number): Range; @@ -142,6 +142,19 @@ export interface IViewModel { getRichTextToCopy(modelRanges: Range[], emptySelectionClipboard: boolean): { html: string, mode: string } | null; } +export class MinimapLinesRenderingData { + public readonly tabSize: number; + public readonly data: Array; + + constructor( + tabSize: number, + data: Array + ) { + this.tabSize = tabSize; + this.data = data; + } +} + export class ViewLineData { _viewLineDataBrand: void; diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index ebd648563971..ed3b33825357 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -19,7 +19,7 @@ import { MinimapTokensColorTracker } from 'vs/editor/common/viewModel/minimapTok import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { ViewLayout } from 'vs/editor/common/viewLayout/viewLayout'; import { IViewModelLinesCollection, IdentityLinesCollection, SplitLinesCollection, ILineBreaksComputerFactory } from 'vs/editor/common/viewModel/splitLinesCollection'; -import { ICoordinatesConverter, IOverviewRulerDecorations, IViewModel, ViewLineData, ViewLineRenderingData, ViewModelDecoration } from 'vs/editor/common/viewModel/viewModel'; +import { ICoordinatesConverter, IOverviewRulerDecorations, IViewModel, MinimapLinesRenderingData, ViewLineData, ViewLineRenderingData, ViewModelDecoration } from 'vs/editor/common/viewModel/viewModel'; import { ViewModelDecorations } from 'vs/editor/common/viewModel/viewModelDecorations'; import { RunOnceScheduler } from 'vs/base/common/async'; import * as platform from 'vs/base/common/platform'; @@ -587,8 +587,12 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel return this.lines.getViewLineData(lineNumber); } - public getMinimapLinesRenderingData(startLineNumber: number, endLineNumber: number, needed: boolean[]): (ViewLineData | null)[] { - return this.lines.getViewLinesData(startLineNumber, endLineNumber, needed); + public getMinimapLinesRenderingData(startLineNumber: number, endLineNumber: number, needed: boolean[]): MinimapLinesRenderingData { + let result = this.lines.getViewLinesData(startLineNumber, endLineNumber, needed); + return new MinimapLinesRenderingData( + this.getTabSize(), + result + ); } public getAllOverviewRulerDecorations(theme: EditorTheme): IOverviewRulerDecorations { From b08cb07f96c76fa4092d356daafd68dea39b85a0 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 13 Feb 2020 13:55:47 +0100 Subject: [PATCH 011/109] scm: use empty views --- .../browser/parts/views/viewPaneContainer.ts | 30 +++++-- .../contrib/scm/browser/media/scmViewlet.css | 11 --- .../contrib/scm/browser/scmViewlet.ts | 86 +++++++++++++------ 3 files changed, 82 insertions(+), 45 deletions(-) diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 1ed47c4dfbc8..54844f6ead48 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -70,22 +70,36 @@ class EmptyViewsController { private _onDidChange = new Emitter(); readonly onDidChange = this._onDidChange.event; - private viewItems: IEmptyViewItem[] = []; - get views(): IViewContentDescriptor[] { return this.viewItems.map(v => v.view); } + private items: IEmptyViewItem[] = []; + get viewContentDescriptors(): IViewContentDescriptor[] { + return this.items + .filter(v => v.visible) + .map(v => v.view); + } + private contextKeyService: IContextKeyService; + private countKey: IContextKey; private disposables = new DisposableStore(); constructor( private id: string, - @IContextKeyService private contextKeyService: IContextKeyService, + @IContextKeyService contextKeyService: IContextKeyService, ) { + this.contextKeyService = contextKeyService.createScoped(); + this.disposables.add(this.contextKeyService); + + this.countKey = this.contextKeyService.createKey('view.emptyViewContentCount', 0); + contextKeyService.onDidChangeContext(this.onDidChangeContext, this, this.disposables); Event.filter(viewsRegistry.onDidChangeEmptyViewContent, id => id === this.id)(this.onDidChangeEmptyViewContent, this, this.disposables); this.onDidChangeEmptyViewContent(); } private onDidChangeEmptyViewContent(): void { - this.viewItems = viewsRegistry.getEmptyViewContent(this.id).map(view => ({ + const contentDescriptors = viewsRegistry.getEmptyViewContent(this.id); + this.countKey.set(contentDescriptors.length); + + this.items = contentDescriptors.map(view => ({ view, visible: view.when ? this.contextKeyService.contextMatchesRules(view.when) : true })); @@ -96,7 +110,7 @@ class EmptyViewsController { private onDidChangeContext(): void { let didChange = false; - for (const item of this.viewItems) { + for (const item of this.items) { if (!item.view.when) { continue; } @@ -277,6 +291,10 @@ export abstract class ViewPane extends Pane implements IView { this.updateEmptyContents(); } + protected layoutBody(height: number, width: number): void { + // noop + } + protected getProgressLocation(): string { return this.viewDescriptorService.getViewContainer(this.id)!.id; } @@ -352,7 +370,7 @@ export abstract class ViewPane extends Pane implements IView { return; } - const contents = viewsRegistry.getEmptyViewContent(this.id); + const contents = this.emptyViewsController.viewContentDescriptors; if (contents.length === 0) { removeClass(this.bodyContainer, 'empty'); diff --git a/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css b/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css index 482b66fb18d9..9c94a5c49eba 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css +++ b/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css @@ -8,17 +8,6 @@ flex: 1; } -.scm-viewlet .empty-message { - box-sizing: border-box; - height: 100%; - padding: 10px 22px 0 22px; -} - -.scm-viewlet:not(.empty) .empty-message, -.scm-viewlet.empty .monaco-pane-view { - display: none; -} - .scm-viewlet .scm-status { height: 100%; position: relative; diff --git a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts index 128c0029152f..7206af16036e 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts @@ -6,12 +6,11 @@ import 'vs/css!./media/scmViewlet'; import { localize } from 'vs/nls'; import { Event, Emitter } from 'vs/base/common/event'; -import { append, $, toggleClass, addClasses } from 'vs/base/browser/dom'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { VIEWLET_ID, ISCMService, ISCMRepository } from 'vs/workbench/contrib/scm/common/scm'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { MenuItemAction } from 'vs/platform/actions/common/actions'; @@ -25,13 +24,16 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IViewsRegistry, Extensions, IViewDescriptorService } from 'vs/workbench/common/views'; +import { IViewsRegistry, Extensions, IViewDescriptorService, IViewDescriptor } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; import { RepositoryPane, RepositoryViewDescriptor } from 'vs/workbench/contrib/scm/browser/repositoryPane'; import { MainPaneDescriptor, MainPane, IViewModel } from 'vs/workbench/contrib/scm/browser/mainPane'; -import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { ViewPaneContainer, IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import type { IAddedViewDescriptorRef, IViewDescriptorRef } from 'vs/workbench/browser/parts/views/views'; import { debounce } from 'vs/base/common/decorators'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { addClass } from 'vs/base/browser/dom'; export interface ISpliceEvent { index: number; @@ -39,12 +41,45 @@ export interface ISpliceEvent { elements: T[]; } +export class EmptyPane extends ViewPane { + + static readonly ID = 'scm'; + static readonly TITLE = localize('scm providers', "Source Control Providers"); + + constructor( + options: IViewPaneOptions, + @IKeybindingService keybindingService: IKeybindingService, + @IContextMenuService contextMenuService: IContextMenuService, + @IConfigurationService configurationService: IConfigurationService, + @IContextKeyService contextKeyService: IContextKeyService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @IInstantiationService instantiationService: IInstantiationService, + @IOpenerService openerService: IOpenerService, + @IThemeService themeService: IThemeService, + ) { + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); + } + + isEmpty(): boolean { + return true; + } +} + +export class EmptyPaneDescriptor implements IViewDescriptor { + readonly id = EmptyPane.ID; + readonly name = EmptyPane.TITLE; + readonly ctorDescriptor = new SyncDescriptor(EmptyPane); + readonly canToggleVisibility = true; + readonly hideByDefault = false; + readonly order = -1000; + readonly workspace = true; + readonly when = ContextKeyExpr.equals('scm.providerCount', 0); +} + export class SCMViewPaneContainer extends ViewPaneContainer implements IViewModel { private static readonly STATE_KEY = 'workbench.scm.views.state'; - private el!: HTMLElement; - private message: HTMLElement; private menus: SCMMenus; private _repositories: ISCMRepository[] = []; @@ -94,9 +129,14 @@ export class SCMViewPaneContainer extends ViewPaneContainer implements IViewMode this.menus = instantiationService.createInstance(SCMMenus, undefined); this._register(this.menus.onDidChangeTitle(this.updateTitleArea, this)); - this.message = $('.empty-message', { tabIndex: 0 }, localize('no open repo', "No source control providers registered.")); - const viewsRegistry = Registry.as(Extensions.ViewsRegistry); + + viewsRegistry.registerEmptyViewContent(EmptyPane.ID, { + content: localize('no open repo', "No source control providers registered."), + when: ContextKeyExpr.equals('view.emptyViewContentCount', 1) + }); + + viewsRegistry.registerViews([new EmptyPaneDescriptor()], this.viewContainer); viewsRegistry.registerViews([new MainPaneDescriptor(this)], this.viewContainer); this._register(configurationService.onDidChangeConfiguration(e => { @@ -113,11 +153,7 @@ export class SCMViewPaneContainer extends ViewPaneContainer implements IViewMode create(parent: HTMLElement): void { super.create(parent); - - this.el = parent; - addClasses(parent, 'scm-viewlet', 'empty'); - append(parent, this.message); - + addClass(parent, 'scm-viewlet'); this._register(this.scmService.onDidAddRepository(this.onDidAddRepository, this)); this._register(this.scmService.onDidRemoveRepository(this.onDidRemoveRepository, this)); this.scmService.repositories.forEach(r => this.onDidAddRepository(r)); @@ -156,9 +192,7 @@ export class SCMViewPaneContainer extends ViewPaneContainer implements IViewMode } private onDidChangeRepositories(): void { - const repositoryCount = this.repositories.length; - toggleClass(this.el, 'empty', repositoryCount === 0); - this.repositoryCountKey.set(repositoryCount); + this.repositoryCountKey.set(this.repositories.length); } private onDidShowView(e: IAddedViewDescriptorRef[]): void { @@ -187,23 +221,19 @@ export class SCMViewPaneContainer extends ViewPaneContainer implements IViewMode } focus(): void { - if (this.repositoryCountKey.get()! === 0) { - this.message.focus(); - } else { - const repository = this.visibleRepositories[0]; + const repository = this.visibleRepositories[0]; - if (repository) { - const pane = this.panes - .filter(pane => pane instanceof RepositoryPane && pane.repository === repository)[0] as RepositoryPane | undefined; + if (repository) { + const pane = this.panes + .filter(pane => pane instanceof RepositoryPane && pane.repository === repository)[0] as RepositoryPane | undefined; - if (pane) { - pane.focus(); - } else { - super.focus(); - } + if (pane) { + pane.focus(); } else { super.focus(); } + } else { + super.focus(); } } From 52d3df6bf2416ed8b89f50944b05719d3e9adce8 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 13 Feb 2020 15:32:45 +0100 Subject: [PATCH 012/109] empty view: placeholder when --- extensions/git/package.json | 9 +++++ .../browser/parts/views/viewPaneContainer.ts | 39 +++++++++++-------- src/vs/workbench/common/views.ts | 2 +- .../contrib/scm/browser/scmViewlet.ts | 4 +- 4 files changed, 35 insertions(+), 19 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index c1cd8b5135c0..c45a83e1037b 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1761,6 +1761,15 @@ 72 ] } + }, + "documentation": { + "view": [ + { + "view": "workbench.scm", + "contents": "Git is installed, but you currently don't have a folder open.\n[Open Folder](command:vscode.openFolder)", + "when": "workbenchState == empty" + } + ] } }, "dependencies": { diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 54844f6ead48..d380b02c9e8f 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -60,8 +60,8 @@ export interface IViewPaneOptions extends IPaneOptions { const viewsRegistry = Registry.as(ViewContainerExtensions.ViewsRegistry); -interface IEmptyViewItem { - readonly view: IViewContentDescriptor; +interface IItem { + readonly descriptor: IViewContentDescriptor; visible: boolean; } @@ -70,15 +70,19 @@ class EmptyViewsController { private _onDidChange = new Emitter(); readonly onDidChange = this._onDidChange.event; - private items: IEmptyViewItem[] = []; + private placeholderItem: IItem | undefined; + private items: IItem[] = []; get viewContentDescriptors(): IViewContentDescriptor[] { - return this.items - .filter(v => v.visible) - .map(v => v.view); + const visibleItems = this.items.filter(v => v.visible); + + if (visibleItems.length === 0 && this.placeholderItem) { + return [this.placeholderItem.descriptor]; + } + + return visibleItems.map(v => v.descriptor); } private contextKeyService: IContextKeyService; - private countKey: IContextKey; private disposables = new DisposableStore(); constructor( @@ -88,8 +92,6 @@ class EmptyViewsController { this.contextKeyService = contextKeyService.createScoped(); this.disposables.add(this.contextKeyService); - this.countKey = this.contextKeyService.createKey('view.emptyViewContentCount', 0); - contextKeyService.onDidChangeContext(this.onDidChangeContext, this, this.disposables); Event.filter(viewsRegistry.onDidChangeEmptyViewContent, id => id === this.id)(this.onDidChangeEmptyViewContent, this, this.disposables); this.onDidChangeEmptyViewContent(); @@ -97,12 +99,17 @@ class EmptyViewsController { private onDidChangeEmptyViewContent(): void { const contentDescriptors = viewsRegistry.getEmptyViewContent(this.id); - this.countKey.set(contentDescriptors.length); - this.items = contentDescriptors.map(view => ({ - view, - visible: view.when ? this.contextKeyService.contextMatchesRules(view.when) : true - })); + this.items = []; + + for (const descriptor of contentDescriptors) { + if (descriptor.when === 'placeholder') { + this.placeholderItem = { descriptor, visible: true }; + } else { + const visible = descriptor.when ? this.contextKeyService.contextMatchesRules(descriptor.when) : true; + this.items.push({ descriptor, visible }); + } + } this._onDidChange.fire(); } @@ -111,11 +118,11 @@ class EmptyViewsController { let didChange = false; for (const item of this.items) { - if (!item.view.when) { + if (!item.descriptor.when || item.descriptor.when === 'placeholder') { continue; } - const visible = this.contextKeyService.contextMatchesRules(item.view.when); + const visible = this.contextKeyService.contextMatchesRules(item.descriptor.when); if (item.visible === visible) { continue; diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 0b1f83c1a93b..d1c4eba42949 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -213,7 +213,7 @@ export interface IViewDescriptorCollection extends IDisposable { export interface IViewContentDescriptor { readonly content: string; - readonly when?: ContextKeyExpr; + readonly when?: ContextKeyExpr | 'placeholder'; } export interface IViewsRegistry { diff --git a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts index 7206af16036e..26a7fd69aab8 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts @@ -43,7 +43,7 @@ export interface ISpliceEvent { export class EmptyPane extends ViewPane { - static readonly ID = 'scm'; + static readonly ID = 'workbench.scm'; static readonly TITLE = localize('scm providers', "Source Control Providers"); constructor( @@ -133,7 +133,7 @@ export class SCMViewPaneContainer extends ViewPaneContainer implements IViewMode viewsRegistry.registerEmptyViewContent(EmptyPane.ID, { content: localize('no open repo', "No source control providers registered."), - when: ContextKeyExpr.equals('view.emptyViewContentCount', 1) + when: 'placeholder' }); viewsRegistry.registerViews([new EmptyPaneDescriptor()], this.viewContainer); From 3e8bb6cfd452f6f0082e8c2757c03d9f73618c42 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 13 Feb 2020 16:03:18 +0100 Subject: [PATCH 013/109] git: more empty view documentation --- extensions/git/package.json | 14 ++++++++++++-- extensions/git/package.nls.json | 5 ++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index c45a83e1037b..c1ad297254f2 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1766,8 +1766,18 @@ "view": [ { "view": "workbench.scm", - "contents": "Git is installed, but you currently don't have a folder open.\n[Open Folder](command:vscode.openFolder)", - "when": "workbenchState == empty" + "contents": "%view.workbench.scm.empty%", + "when": "config.git.enabled && workbenchState == empty" + }, + { + "view": "workbench.scm", + "contents": "%view.workbench.scm.folder%", + "when": "config.git.enabled && workbenchState == folder" + }, + { + "view": "workbench.scm", + "contents": "%view.workbench.scm.workspace%", + "when": "config.git.enabled && workbenchState == workspace" } ] } diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index f3d755196534..2b787fe82ae5 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -146,5 +146,8 @@ "colors.untracked": "Color for untracked resources.", "colors.ignored": "Color for ignored resources.", "colors.conflict": "Color for resources with conflicts.", - "colors.submodule": "Color for submodule resources." + "colors.submodule": "Color for submodule resources.", + "view.workbench.scm.empty": "In order to use git features, you can open a folder containing a git repository or clone from a URL.\n[Open Folder](command:vscode.openFolder)\n[Clone from URL](command:git.clone)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", + "view.workbench.scm.folder": "The folder currently open doesn't have a git repository.\n[Initialize Repository](command:git.init)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", + "view.workbench.scm.workspace": "The workspace currently open doesn't have any folders containing git repositories.\n[Initialize Repository](command:git.init)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm)." } From 429fee578d2e02246cf2363965fec17a14846b4e Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 13 Feb 2020 16:09:03 +0100 Subject: [PATCH 014/109] Recreate sampling incrementally --- .../browser/viewParts/minimap/minimap.ts | 172 +++++++++++++++--- .../viewParts/minimap/minimapCharRenderer.ts | 6 +- .../browser/view/minimapCharRenderer.test.ts | 4 +- 3 files changed, 156 insertions(+), 26 deletions(-) diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index 8e1846c86412..3d2ffdd9a5af 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -234,7 +234,6 @@ class MinimapLayout { scrollHeight: number, previousLayout: MinimapLayout | null ): MinimapLayout { - // console.log(`create - ${viewportStartLineNumber}, ${viewportEndLineNumber}, ${viewportHeight}, ${viewportContainsWhitespaceGaps}, ${lineCount}, ${scrollTop}, ${scrollHeight}`); const pixelRatio = options.pixelRatio; const minimapLineHeight = options.minimapLineHeight; const minimapLinesFitting = Math.floor(options.canvasInnerHeight / minimapLineHeight); @@ -502,9 +501,10 @@ export class Minimap extends ViewPart implements IMinimapModel { private _selections: Selection[]; private _minimapSelections: Selection[]; - private _samplingRatio!: number; - private _minimapLines!: number[]; - private _isSampling = false;//true; + private _samplingRatio: number; + private _minimapLines: number[]; + private _isSampling = false; + private _shouldCheckSampling: boolean; public readonly tokensColorTracker: MinimapTokensColorTracker; public options: MinimapOptions; @@ -517,7 +517,10 @@ export class Minimap extends ViewPart implements IMinimapModel { this._selections = []; this._minimapSelections = []; - this._recreateLineMapping(); + this._samplingRatio = 1; + this._minimapLines = []; + this._shouldCheckSampling = false; + this._recreateLineSampling(null); this.tokensColorTracker = MinimapTokensColorTracker.getInstance(); this.options = new MinimapOptions(this._context.configuration, this._context.theme, this.tokensColorTracker, this._isSampling); @@ -594,7 +597,8 @@ export class Minimap extends ViewPart implements IMinimapModel { if (changeStartIndex <= changeEndIndex) { this._actual.onLinesChanged(changeStartIndex + 1, changeEndIndex + 1); } - return this._checkMapping(); + this._shouldCheckSampling = true; + return true; } else { return this._actual.onLinesDeleted(e.fromLineNumber, e.toLineNumber); } @@ -609,7 +613,8 @@ export class Minimap extends ViewPart implements IMinimapModel { } this._minimapLines[i] += insertedLineCount; } - return this._checkMapping(); + this._shouldCheckSampling = true; + return true; } else { return this._actual.onLinesInserted(e.fromLineNumber, e.toLineNumber); } @@ -651,7 +656,10 @@ export class Minimap extends ViewPart implements IMinimapModel { // --- end event handlers public prepareRender(ctx: RenderingContext): void { - // nothing to read + if (this._shouldCheckSampling) { + this._shouldCheckSampling = false; + this._recreateLineSampling(this._minimapLines); + } } public render(ctx: RestrictedRenderingContext): void { @@ -684,12 +692,23 @@ export class Minimap extends ViewPart implements IMinimapModel { //#region IMinimapModel - private _checkMapping(): boolean { - // console.log(`TODO: I SHOULD ADJUST THE MAPPING!!!`, this._minimapLines); - return false; + private _createLineSampling(minimapLineCount: number, modelLineCount: number, ratio: number): number[] { + let result: number[] = []; + result[0] = 1; + if (minimapLineCount > 1) { + const halfRatio = ratio / 2; + for (let i = 0, lastIndex = minimapLineCount - 1; i < lastIndex; i++) { + result[i] = Math.round(i * ratio + halfRatio); + } + result[minimapLineCount - 1] = modelLineCount; + } + return result; } - private _recreateLineMapping(): void { + private _recreateLineSampling(oldMinimapLines: number[] | null): void { + // generate at most 10 events, if there are more than 10 changes, just flush all previous data + const MAX_EVENT_COUNT = 10; + const options = this._context.configuration.options; const layoutInfo = options.get(EditorOption.layoutInfo); const pixelRatio = options.get(EditorOption.pixelRatio); @@ -702,10 +721,113 @@ export class Minimap extends ViewPart implements IMinimapModel { const minimapLineCount = Math.floor(modelLineCount / desiredRatio); const ratio = modelLineCount / minimapLineCount; + if (!oldMinimapLines) { + this._minimapLines = this._createLineSampling(minimapLineCount, modelLineCount, ratio); + this._samplingRatio = ratio; + return; + } + + const halfRatio = ratio / 2; + const oldLength = oldMinimapLines.length; let result: number[] = []; + let oldIndex = 0; + let oldDeltaLineCount = 0; + let minModelLineNumber = 1; + let eventCount = 0; + let currentDeleteStart = 0, currentDeleteEnd = 0; + let currentInsertStart = 0, currentInsertEnd = 0; for (let i = 0; i < minimapLineCount; i++) { - const desiredLine = Math.min(modelLineCount, Math.round((i + 1) * ratio)); - result[i] = desiredLine; + const fromModelLineNumber = Math.max(minModelLineNumber, Math.round(i * ratio)); + const toModelLineNumber = Math.max(fromModelLineNumber, Math.round((i + 1) * ratio)); + + while (oldIndex < oldLength && oldMinimapLines[oldIndex] < fromModelLineNumber) { + if (eventCount < MAX_EVENT_COUNT) { + if (currentInsertEnd !== 0) { + // must deliver previous insert event + this._actual.onLinesInserted(currentInsertStart, currentInsertEnd); + oldDeltaLineCount += (currentInsertEnd - currentInsertStart + 1); + currentInsertStart = 0; + currentInsertEnd = 0; + eventCount++; + } + + if (currentDeleteStart === 0) { + currentDeleteStart = oldIndex + 1 + oldDeltaLineCount; + currentDeleteEnd = currentDeleteStart; + } else { + currentDeleteEnd++; + } + } + oldIndex++; + } + + let selectedModelLineNumber: number; + if (oldIndex < oldLength && oldMinimapLines[oldIndex] <= toModelLineNumber) { + // reuse the old sampled line + selectedModelLineNumber = oldMinimapLines[oldIndex]; + oldIndex++; + } else { + if (i === 0) { + selectedModelLineNumber = 1; + } else if (i + 1 === minimapLineCount) { + selectedModelLineNumber = modelLineCount; + } else { + selectedModelLineNumber = Math.round(i * ratio + halfRatio); + } + if (eventCount < MAX_EVENT_COUNT) { + if (currentDeleteEnd !== 0) { + // must deliver previous delete event + this._actual.onLinesDeleted(currentDeleteStart, currentDeleteEnd); + oldDeltaLineCount -= (currentDeleteEnd - currentDeleteStart + 1); + currentDeleteStart = 0; + currentDeleteEnd = 0; + eventCount++; + } + + if (currentInsertStart === 0) { + currentInsertStart = oldIndex + 1 + oldDeltaLineCount; + currentInsertEnd = currentInsertStart; + } else { + currentInsertEnd++; + } + } + } + + result[i] = selectedModelLineNumber; + if (selectedModelLineNumber === modelLineCount) { + minModelLineNumber = selectedModelLineNumber; + } else { + minModelLineNumber = selectedModelLineNumber + 1; + } + } + + if (eventCount < MAX_EVENT_COUNT) { + if (currentInsertEnd !== 0) { + // must deliver previous insert event + this._actual.onLinesInserted(currentInsertStart, currentInsertEnd); + oldDeltaLineCount += (currentInsertEnd - currentInsertStart + 1); + currentInsertStart = 0; + currentInsertEnd = 0; + } + while (oldIndex < oldLength) { + if (currentDeleteStart === 0) { + currentDeleteStart = oldIndex + 1 + oldDeltaLineCount; + currentDeleteEnd = currentDeleteStart; + } else { + currentDeleteEnd++; + } + oldIndex++; + } + if (currentDeleteEnd !== 0) { + // must deliver previous delete event + this._actual.onLinesDeleted(currentDeleteStart, currentDeleteEnd); + oldDeltaLineCount -= (currentDeleteEnd - currentDeleteStart + 1); + currentDeleteStart = 0; + currentDeleteEnd = 0; + } + } else { + // too many events, just give up + this._actual.onFlushed(); } this._samplingRatio = ratio; @@ -1288,8 +1410,6 @@ class InnerMinimap extends Disposable { } private renderLines(layout: MinimapLayout): RenderData | null { - const renderMinimap = this._model.options.renderMinimap; - const charRenderer = this._model.options.charRenderer(); const startLineNumber = layout.startLineNumber; const endLineNumber = layout.endLineNumber; const minimapLineHeight = this._model.options.minimapLineHeight; @@ -1322,7 +1442,13 @@ class InnerMinimap extends Disposable { const lineInfo = this._model.getMinimapLinesRenderingData(startLineNumber, endLineNumber, needed); const tabSize = this._model.getOptions().tabSize; const background = this._model.options.backgroundColor; - const useLighterFont = this._model.tokensColorTracker.backgroundIsLight(); + const tokensColorTracker = this._model.tokensColorTracker; + const useLighterFont = tokensColorTracker.backgroundIsLight(); + const renderMinimap = this._model.options.renderMinimap; + const charRenderer = this._model.options.charRenderer(); + const isSampling = this._model.options.isSampling; + const fontScale = this._model.options.fontScale; + const minimapCharWidth = this._model.options.minimapCharWidth; // Render the rest of lines let dy = 0; @@ -1334,13 +1460,14 @@ class InnerMinimap extends Disposable { background, useLighterFont, renderMinimap, - this._model.options.minimapCharWidth, - this._model.tokensColorTracker, + minimapCharWidth, + tokensColorTracker, charRenderer, dy, tabSize, lineInfo[lineIndex]!, - this._model.options.fontScale + fontScale, + isSampling ); } renderedLines[lineIndex] = new MinimapLine(dy); @@ -1466,7 +1593,8 @@ class InnerMinimap extends Disposable { dy: number, tabSize: number, lineData: ViewLineData, - fontScale: number + fontScale: number, + isSampling: boolean ): void { const content = lineData.content; const tokens = lineData.tokens; @@ -1504,7 +1632,7 @@ class InnerMinimap extends Disposable { if (renderMinimap === RenderMinimap.Blocks) { minimapCharRenderer.blockRenderChar(target, dx, dy, tokenColor, backgroundColor, useLighterFont); } else { // RenderMinimap.Text - minimapCharRenderer.renderChar(target, dx, dy, charCode, tokenColor, backgroundColor, fontScale, useLighterFont); + minimapCharRenderer.renderChar(target, dx, dy, charCode, tokenColor, backgroundColor, fontScale, useLighterFont, isSampling); } dx += charWidth; diff --git a/src/vs/editor/browser/viewParts/minimap/minimapCharRenderer.ts b/src/vs/editor/browser/viewParts/minimap/minimapCharRenderer.ts index 6feeecc03ac7..483abbaac3b2 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimapCharRenderer.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimapCharRenderer.ts @@ -34,7 +34,8 @@ export class MinimapCharRenderer { color: RGBA8, backgroundColor: RGBA8, fontScale: number, - useLighterFont: boolean + useLighterFont: boolean, + force1pxHeight: boolean ): void { const charWidth = Constants.BASE_CHAR_WIDTH * this.scale; const charHeight = Constants.BASE_CHAR_HEIGHT * this.scale; @@ -60,7 +61,8 @@ export class MinimapCharRenderer { let sourceOffset = charIndex * charWidth * charHeight; let row = dy * destWidth + dx * Constants.RGBA_CHANNELS_CNT; - for (let y = 0; y < charHeight; y++) { + const renderHeight = (force1pxHeight ? 1 : charHeight); + for (let y = 0; y < renderHeight; y++) { let column = row; for (let x = 0; x < charWidth; x++) { const c = charData[sourceOffset++] / 255; diff --git a/src/vs/editor/test/browser/view/minimapCharRenderer.test.ts b/src/vs/editor/test/browser/view/minimapCharRenderer.test.ts index b0b0762997c4..cde867901417 100644 --- a/src/vs/editor/test/browser/view/minimapCharRenderer.test.ts +++ b/src/vs/editor/test/browser/view/minimapCharRenderer.test.ts @@ -78,7 +78,7 @@ suite('MinimapCharRenderer', () => { imageData.data[4 * i + 2] = background.b; imageData.data[4 * i + 3] = 255; } - renderer.renderChar(imageData, 0, 0, 'd'.charCodeAt(0), color, background, 2, false); + renderer.renderChar(imageData, 0, 0, 'd'.charCodeAt(0), color, background, 2, false, false); let actual: number[] = []; for (let i = 0; i < imageData.data.length; i++) { @@ -108,7 +108,7 @@ suite('MinimapCharRenderer', () => { imageData.data[4 * i + 3] = 255; } - renderer.renderChar(imageData, 0, 0, 'd'.charCodeAt(0), color, background, 1, false); + renderer.renderChar(imageData, 0, 0, 'd'.charCodeAt(0), color, background, 1, false, false); let actual: number[] = []; for (let i = 0; i < imageData.data.length; i++) { From d946716fe2830378769c3c62fc02fdc63cae8167 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 13 Feb 2020 16:13:10 +0100 Subject: [PATCH 015/109] improve git empty view docs --- extensions/git/package.json | 5 +++++ extensions/git/package.nls.json | 1 + 2 files changed, 6 insertions(+) diff --git a/extensions/git/package.json b/extensions/git/package.json index c1ad297254f2..f450b7b4f700 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1764,6 +1764,11 @@ }, "documentation": { "view": [ + { + "view": "workbench.scm", + "contents": "%view.workbench.scm.disabled%", + "when": "!config.git.enabled" + }, { "view": "workbench.scm", "contents": "%view.workbench.scm.empty%", diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 2b787fe82ae5..62b2b21357d3 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -147,6 +147,7 @@ "colors.ignored": "Color for ignored resources.", "colors.conflict": "Color for resources with conflicts.", "colors.submodule": "Color for submodule resources.", + "view.workbench.scm.disabled": "If you would like to use git features, please enable git in your [settings](command:workbench.action.openSettings?%5B%22git.enabled%22%5D).\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", "view.workbench.scm.empty": "In order to use git features, you can open a folder containing a git repository or clone from a URL.\n[Open Folder](command:vscode.openFolder)\n[Clone from URL](command:git.clone)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", "view.workbench.scm.folder": "The folder currently open doesn't have a git repository.\n[Initialize Repository](command:git.init)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", "view.workbench.scm.workspace": "The workspace currently open doesn't have any folders containing git repositories.\n[Initialize Repository](command:git.init)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm)." From ca9be9fc65be63d5a46cd92db7f01926c182bedc Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 13 Feb 2020 16:52:51 +0100 Subject: [PATCH 016/109] command: workbench.extensions.search --- .../browser/extensions.contribution.ts | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 3228cb72d0c7..49ffa58bb985 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -14,7 +14,7 @@ import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } fro import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IOutputChannelRegistry, Extensions as OutputExtensions } from 'vs/workbench/services/output/common/output'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { VIEWLET_ID, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; +import { VIEWLET_ID, IExtensionsWorkbenchService, IExtensionsViewPaneContainer } from 'vs/workbench/contrib/extensions/common/extensions'; import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/browser/extensionsWorkbenchService'; import { OpenExtensionsViewletAction, InstallExtensionsAction, ShowOutdatedExtensionsAction, ShowRecommendedExtensionsAction, ShowRecommendedKeymapExtensionsAction, ShowPopularExtensionsAction, @@ -47,6 +47,7 @@ import { IViewContainersRegistry, ViewContainerLocation, Extensions as ViewConta import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; // Singletons registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService); @@ -290,6 +291,30 @@ CommandsRegistry.registerCommand({ } }); +CommandsRegistry.registerCommand({ + id: 'workbench.extensions.search', + description: { + description: localize('workbench.extensions.search.description', "Search for a specific extension"), + args: [ + { + name: localize('workbench.extensions.search.arg.name', "Query to use in search"), + schema: { 'type': 'string' } + } + ] + }, + handler: async (accessor, query: string = '') => { + const viewletService = accessor.get(IViewletService); + const viewlet = await viewletService.openViewlet(VIEWLET_ID, true); + + if (!viewlet) { + return; + } + + (viewlet.getViewPaneContainer() as IExtensionsViewPaneContainer).search(query); + viewlet.focus(); + } +}); + // File menu registration MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { From eab81b4a31148ff4fe69fc8efd3076feb82154c3 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 13 Feb 2020 16:53:08 +0100 Subject: [PATCH 017/109] git missing docs --- extensions/git/package.json | 11 ++++++++--- extensions/git/package.nls.json | 1 + extensions/git/src/main.ts | 1 + 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index f450b7b4f700..922a16d0bf0b 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1769,20 +1769,25 @@ "contents": "%view.workbench.scm.disabled%", "when": "!config.git.enabled" }, + { + "view": "workbench.scm", + "contents": "%view.workbench.scm.missing%", + "when": "config.git.enabled && git.missing" + }, { "view": "workbench.scm", "contents": "%view.workbench.scm.empty%", - "when": "config.git.enabled && workbenchState == empty" + "when": "config.git.enabled && !git.missing && workbenchState == empty" }, { "view": "workbench.scm", "contents": "%view.workbench.scm.folder%", - "when": "config.git.enabled && workbenchState == folder" + "when": "config.git.enabled && !git.missing && workbenchState == folder" }, { "view": "workbench.scm", "contents": "%view.workbench.scm.workspace%", - "when": "config.git.enabled && workbenchState == workspace" + "when": "config.git.enabled && !git.missing && workbenchState == workspace" } ] } diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 62b2b21357d3..55b1b423f20c 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -147,6 +147,7 @@ "colors.ignored": "Color for ignored resources.", "colors.conflict": "Color for resources with conflicts.", "colors.submodule": "Color for submodule resources.", + "view.workbench.scm.missing": "A valid git installation was not detected, more details can be found in the [git output](command:git.showOutput).\nPlease [install git](https://git-scm.com/), or learn more about how to use Git and source control in VS Code in [our docs](https://aka.ms/vscode-scm).\nIf you're using a different version control system, you can [search the Marketplace](command:workbench.extensions.search?%22%40category%3A%5C%22scm%20providers%5C%22%22) for additional extensions.", "view.workbench.scm.disabled": "If you would like to use git features, please enable git in your [settings](command:workbench.action.openSettings?%5B%22git.enabled%22%5D).\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", "view.workbench.scm.empty": "In order to use git features, you can open a folder containing a git repository or clone from a URL.\n[Open Folder](command:vscode.openFolder)\n[Clone from URL](command:git.clone)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", "view.workbench.scm.folder": "The folder currently open doesn't have a git repository.\n[Initialize Repository](command:git.init)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index 7bb5081d12f3..ddf16d76aacb 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -175,6 +175,7 @@ export async function activate(context: ExtensionContext): Promise console.warn(err.message); outputChannel.appendLine(err.message); + commands.executeCommand('setContext', 'git.missing', true); warnAboutMissingGit(); return new GitExtensionImpl(); From db2093d705b133ee9c4bbcb687e7f425c72d5b90 Mon Sep 17 00:00:00 2001 From: Alexander Schmidt-Krulig Date: Thu, 13 Feb 2020 17:34:44 +0100 Subject: [PATCH 018/109] Add entireDocument minimap option --- src/vs/editor/common/config/editorOptions.ts | 11 +++++++++++ src/vs/monaco.d.ts | 4 ++++ 2 files changed, 15 insertions(+) diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 50d6dd6c3e0b..29e137cf5d86 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -1995,6 +1995,10 @@ export interface IEditorMinimapOptions { * Relative size of the font in the minimap. Defaults to 1. */ scale?: number; + /** + * Minimap covers entire document. + */ + entireDocument?: boolean; } export type EditorMinimapOptions = Readonly>; @@ -2009,6 +2013,7 @@ class EditorMinimap extends BaseEditorOption>; From 29d7bdf5c7bd9f0e8039513924f636bc13a31b9d Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 13 Feb 2020 17:35:30 +0100 Subject: [PATCH 019/109] Read and honor entireDocument --- .../browser/viewParts/minimap/minimap.ts | 44 ++++++++++++++----- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index 3d2ffdd9a5af..4cf1bdc15711 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -501,9 +501,9 @@ export class Minimap extends ViewPart implements IMinimapModel { private _selections: Selection[]; private _minimapSelections: Selection[]; + private _isSampling: boolean; private _samplingRatio: number; private _minimapLines: number[]; - private _isSampling = false; private _shouldCheckSampling: boolean; public readonly tokensColorTracker: MinimapTokensColorTracker; @@ -517,10 +517,11 @@ export class Minimap extends ViewPart implements IMinimapModel { this._selections = []; this._minimapSelections = []; + this._isSampling = false; this._samplingRatio = 1; this._minimapLines = []; this._shouldCheckSampling = false; - this._recreateLineSampling(null); + this._recreateLineSampling(true); this.tokensColorTracker = MinimapTokensColorTracker.getInstance(); this.options = new MinimapOptions(this._context.configuration, this._context.theme, this.tokensColorTracker, this._isSampling); @@ -550,7 +551,10 @@ export class Minimap extends ViewPart implements IMinimapModel { // ---- begin view event handlers public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { - return this._onOptionsMaybeChanged(); + let shouldRender = false; + shouldRender = this._recreateLineSampling(false) || shouldRender; + shouldRender = this._onOptionsMaybeChanged() || shouldRender; + return shouldRender; } public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { this._selections = e.selections; @@ -658,7 +662,7 @@ export class Minimap extends ViewPart implements IMinimapModel { public prepareRender(ctx: RenderingContext): void { if (this._shouldCheckSampling) { this._shouldCheckSampling = false; - this._recreateLineSampling(this._minimapLines); + this._recreateLineSampling(false); } } @@ -705,11 +709,22 @@ export class Minimap extends ViewPart implements IMinimapModel { return result; } - private _recreateLineSampling(oldMinimapLines: number[] | null): void { - // generate at most 10 events, if there are more than 10 changes, just flush all previous data - const MAX_EVENT_COUNT = 10; - + private _recreateLineSampling(ctor: boolean): boolean { + const wasSampling = this._isSampling; const options = this._context.configuration.options; + const minimapOpts = options.get(EditorOption.minimap); + if (!minimapOpts.entireDocument) { + // no sampling! + this._isSampling = false; + this._minimapLines = []; + this._samplingRatio = 1; + if (!ctor && wasSampling) { + // was sampling => sampling stopped + return this._onOptionsMaybeChanged(); + } + return false; + } + const layoutInfo = options.get(EditorOption.layoutInfo); const pixelRatio = options.get(EditorOption.pixelRatio); const lineHeight = options.get(EditorOption.lineHeight); @@ -721,18 +736,25 @@ export class Minimap extends ViewPart implements IMinimapModel { const minimapLineCount = Math.floor(modelLineCount / desiredRatio); const ratio = modelLineCount / minimapLineCount; - if (!oldMinimapLines) { + if (!this._minimapLines || this._minimapLines.length === 0) { + this._isSampling = true; this._minimapLines = this._createLineSampling(minimapLineCount, modelLineCount, ratio); this._samplingRatio = ratio; - return; + if (!ctor && !wasSampling) { + // was not sampling => now is sampling + return this._onOptionsMaybeChanged(); + } + return false; } const halfRatio = ratio / 2; + const oldMinimapLines = this._minimapLines; const oldLength = oldMinimapLines.length; let result: number[] = []; let oldIndex = 0; let oldDeltaLineCount = 0; let minModelLineNumber = 1; + const MAX_EVENT_COUNT = 10; // generate at most 10 events, if there are more than 10 changes, just flush all previous data let eventCount = 0; let currentDeleteStart = 0, currentDeleteEnd = 0; let currentInsertStart = 0, currentInsertEnd = 0; @@ -830,8 +852,10 @@ export class Minimap extends ViewPart implements IMinimapModel { this._actual.onFlushed(); } + this._isSampling = true; this._samplingRatio = ratio; this._minimapLines = result; + return false; } private _modelLineToMinimapLine(lineNumber: number): number { From f8014d495791145f1fac93cc0283737878272129 Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Thu, 13 Feb 2020 10:12:43 -0800 Subject: [PATCH 020/109] Update debug icon, make play bigger --- .../ui/codiconLabel/codicon/codicon.css | 2 +- .../ui/codiconLabel/codicon/codicon.ttf | Bin 56736 -> 56736 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css index e60151c5aa3c..2fa65d719233 100644 --- a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css +++ b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css @@ -5,7 +5,7 @@ @font-face { font-family: "codicon"; - src: url("./codicon.ttf?6caeeccc06315e827f3bff83885456fb") format("truetype"); + src: url("./codicon.ttf?d0510f6ecacbb2788db2b3162273a3d8") format("truetype"); } .codicon[class*='codicon-'] { diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf index a62bc6bd7440ca0455aeb2be4ddfec928d4987a3..f712f5cd0531a353cc21917dd937fa12d375c1dc 100644 GIT binary patch delta 226 zcmZ3mn|Z-*<_QiWGletnFfd5BFfgotlaZR3BL1lV!Nia$GCmJxs>bu%d}ZKfe!&0) zx3X1xVDx4d#?7YOhncT1s50m=STeXxK4fN7&n(EuDkiFG%E&HgY{bNAXXnJu9-!kY ztEj4?=_=`|6TrpdWM`-2$mVCRVyDjJ`0x2#J$s##e-tfUEEQdtjQ?HXV9ftpX2hY$ zINvV7PDdzE-&H|KSVhrQCqPcYF2G60$=p-fTFEJ%U%^06UdYZ)+e1ssL)(r~l+p2D WE7uV%(N|WCD`o#p+}v(1xeNelCO^Lb delta 226 zcmZ3mn|Z-*<_QiWx6bu+Ffd5BFfgotlaZR3BL3i*)x?k~GADDNOUCotd}ZKfe!&0) zx3U~|!syK`jGIllPXi@X8T1$|8C)kHGP9{SS2bm17c>$VWMma%lC!gOV&@3dag|k4 zQPFgh^wbXEVs*5$({^I>Ggq-wXL9`ae6F6o&dEQDmM)fx7Z_vzUNTovW<2)q1s~%U zy8wG_;Q)PC1tDP-MOW=WIR(1_CmknqPi1Q*r+7X^13h^mpjjSTS{~YV@^`Hm^JV{W U)o?L-|GThNo`GR=ySd~t03jJdzW@LL From 615c24ca3a921879ac4f72d3eb60d8d86ae55102 Mon Sep 17 00:00:00 2001 From: Bailey Date: Thu, 13 Feb 2020 11:40:20 -0700 Subject: [PATCH 021/109] Add explorerView check to see if element is visible --- src/vs/base/browser/ui/list/listWidget.ts | 10 ++++++++++ src/vs/base/browser/ui/tree/abstractTree.ts | 13 +++++++++++++ src/vs/base/browser/ui/tree/asyncDataTree.ts | 4 ++++ .../contrib/files/browser/views/explorerView.ts | 6 +++++- 4 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 026344c5e1b6..339cc5af913d 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -1546,6 +1546,16 @@ export class List implements ISpliceable, IDisposable { return this.getFocus().map(i => this.view.element(i)); } + isElementVisible(index: number) { + const viewTop = this.view.getScrollTop(); + const viewBottom = this.view.renderHeight + viewTop; + + const elementTop = this.view.elementTop(index); + const elementBottom = this.view.elementHeight(index) + elementTop; + + return (elementTop >= viewTop && elementBottom <= viewBottom); + } + reveal(index: number, relativeTop?: number): void { if (index < 0 || index >= this.length) { throw new ListError(this.user, `Invalid index ${index}`); diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 9e584efcc560..1032a5a723f7 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -1589,6 +1589,19 @@ export abstract class AbstractTree implements IDisposable this.view.open(indexes, browserEvent); } + isElementVisible(location: TRef) { + const index = this.model.getListIndex(location); + + if (index === -1) { + return false; + } + + const parentNode = this.model.getNode(this.model.getParentNodeLocation(location)); + const parentCollapsed = !parentNode.visible || parentNode.collapsed; + + return this.view.isElementVisible(index) && !parentCollapsed; + } + reveal(location: TRef, relativeTop?: number): void { this.model.expandTo(location); diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index bbdc7bc60540..7edd15a2f62b 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -662,6 +662,10 @@ export class AsyncDataTree implements IDisposable this.tree.open(nodes, browserEvent); } + isElementVisible(element: T) { + return this.tree.isElementVisible(this.getDataNode(element)); + } + reveal(element: T, relativeTop?: number): void { this.tree.reveal(this.getDataNode(element), relativeTop); } diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index f75377476493..cce6ba126a0a 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -675,7 +675,11 @@ export class ExplorerView extends ViewPane { if (item.isDisposed) { return this.onSelectResource(resource, reveal, retry + 1); } - this.tree.reveal(item, 0.5); + + // Don't scroll to the item if it's already visible + if (!this.tree.isElementVisible(item)) { + this.tree.reveal(item, 0.5); + } } this.tree.setFocus([item]); From 28906d62f5edc2a5d02c12b3fc6cd862fae750fb Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 13 Feb 2020 21:04:20 +0100 Subject: [PATCH 022/109] Fix compilation error --- .../editor/test/common/viewLayout/editorLayoutProvider.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts b/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts index cfb8396fe377..7cb925796e39 100644 --- a/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts +++ b/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts @@ -50,6 +50,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { maxColumn: input.minimapMaxColumn, showSlider: 'mouseover', scale: 1, + entireDocument: false }; options._write(EditorOption.minimap, minimapOptions); const scrollbarOptions: InternalEditorScrollbarOptions = { From f5013e5a2a3da1a617ce00a6f63c11e70a48e781 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 13 Feb 2020 22:24:57 +0100 Subject: [PATCH 023/109] Extract sampling computation to a separate class --- .../browser/viewParts/minimap/minimap.ts | 369 ++++++++++-------- 1 file changed, 211 insertions(+), 158 deletions(-) diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index 4cf1bdc15711..04218f80d2c8 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -24,7 +24,7 @@ import { MinimapTokensColorTracker } from 'vs/editor/common/viewModel/minimapTok import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/view/renderingContext'; import { ViewContext, EditorTheme } from 'vs/editor/common/view/viewContext'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; -import { ViewLineData, ViewModelDecoration } from 'vs/editor/common/viewModel/viewModel'; +import { ViewLineData, ViewModelDecoration, IViewModel } from 'vs/editor/common/viewModel/viewModel'; import { minimapSelection, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, minimapBackground } from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { ModelDecorationMinimapOptions } from 'vs/editor/common/model/textModel'; @@ -479,7 +479,7 @@ export interface IMinimapModel { setScrollTop(scrollTop: number): void; } -export interface IMinimapRenderingContext { +interface IMinimapRenderingContext { readonly samplingRatio: number; readonly viewportContainsWhitespaceGaps: boolean; @@ -496,6 +496,175 @@ export interface IMinimapRenderingContext { readonly viewportHeight: number; } +interface SamplingStateLinesDeletedEvent { + type: 'deleted'; + deleteFromLineNumber: number; + deleteToLineNumber: number; +} + +interface SamplingStateLinesInsertedEvent { + type: 'inserted'; + insertFromLineNumber: number; + insertToLineNumber: number; +} + +interface SamplingStateFlushEvent { + type: 'flush'; +} + +type SamplingStateEvent = SamplingStateLinesInsertedEvent | SamplingStateLinesDeletedEvent | SamplingStateFlushEvent; + +class MinimapSamplingState { + + public static compute(configuration: IConfiguration, model: IViewModel, oldMinimapLines: number[] | null): [MinimapSamplingState, SamplingStateEvent[]] { + const options = configuration.options; + const minimapOpts = options.get(EditorOption.minimap); + if (minimapOpts.enabled === false || !minimapOpts.entireDocument) { + return [new MinimapSamplingState(false, 1, []), []]; + } + + const layoutInfo = options.get(EditorOption.layoutInfo); + const pixelRatio = options.get(EditorOption.pixelRatio); + const lineHeight = options.get(EditorOption.lineHeight); + const scrollBeyondLastLine = options.get(EditorOption.scrollBeyondLastLine); + const modelLineCount = model.getLineCount(); + + const extraLinesBeyondLastLine = scrollBeyondLastLine ? (layoutInfo.height / lineHeight - 1) : 0; + const desiredRatio = (modelLineCount + extraLinesBeyondLastLine) / (pixelRatio * layoutInfo.height); + const minimapLineCount = Math.floor(modelLineCount / desiredRatio); + const ratio = modelLineCount / minimapLineCount; + + if (!oldMinimapLines || oldMinimapLines.length === 0) { + let result: number[] = []; + result[0] = 1; + if (minimapLineCount > 1) { + const halfRatio = ratio / 2; + for (let i = 0, lastIndex = minimapLineCount - 1; i < lastIndex; i++) { + result[i] = Math.round(i * ratio + halfRatio); + } + result[minimapLineCount - 1] = modelLineCount; + } + return [new MinimapSamplingState(true, ratio, result), []]; + } + + const halfRatio = ratio / 2; + const oldLength = oldMinimapLines.length; + let result: number[] = []; + let oldIndex = 0; + let oldDeltaLineCount = 0; + let minModelLineNumber = 1; + const MAX_EVENT_COUNT = 10; // generate at most 10 events, if there are more than 10 changes, just flush all previous data + let events: SamplingStateEvent[] = []; + let currentDeleteStart = 0, currentDeleteEnd = 0; + let currentInsertStart = 0, currentInsertEnd = 0; + for (let i = 0; i < minimapLineCount; i++) { + const fromModelLineNumber = Math.max(minModelLineNumber, Math.round(i * ratio)); + const toModelLineNumber = Math.max(fromModelLineNumber, Math.round((i + 1) * ratio)); + + console.log(`LINE AT ${i} SHOULD FIT BTW ${fromModelLineNumber} to ${toModelLineNumber}`); + + while (oldIndex < oldLength && oldMinimapLines[oldIndex] < fromModelLineNumber) { + if (events.length < MAX_EVENT_COUNT) { + if (currentInsertEnd !== 0) { + // generate previous insert event + events.push({ type: 'inserted', insertFromLineNumber: currentInsertStart, insertToLineNumber: currentInsertEnd }); + oldDeltaLineCount += (currentInsertEnd - currentInsertStart + 1); + currentInsertStart = 0; + currentInsertEnd = 0; + } + + if (currentDeleteStart === 0) { + currentDeleteStart = oldIndex + 1 + oldDeltaLineCount; + currentDeleteEnd = currentDeleteStart; + } else { + currentDeleteEnd++; + } + } + console.log(` => deleting ${oldMinimapLines[oldIndex]}`); + oldIndex++; + } + + let selectedModelLineNumber: number; + if (oldIndex < oldLength && oldMinimapLines[oldIndex] <= toModelLineNumber) { + // reuse the old sampled line + selectedModelLineNumber = oldMinimapLines[oldIndex]; + console.log(` => keeping ${oldMinimapLines[oldIndex]}`); + oldIndex++; + } else { + if (i === 0) { + selectedModelLineNumber = 1; + } else if (i + 1 === minimapLineCount) { + selectedModelLineNumber = modelLineCount; + } else { + selectedModelLineNumber = Math.round(i * ratio + halfRatio); + } + console.log(` => inserting ${selectedModelLineNumber}`); + if (events.length < MAX_EVENT_COUNT) { + if (currentDeleteEnd !== 0) { + // generate previous delete event + events.push({ type: 'deleted', deleteFromLineNumber: currentDeleteStart, deleteToLineNumber: currentDeleteEnd }); + oldDeltaLineCount -= (currentDeleteEnd - currentDeleteStart + 1); + currentDeleteStart = 0; + currentDeleteEnd = 0; + } + + if (currentInsertStart === 0) { + currentInsertStart = oldIndex + 1 + oldDeltaLineCount; + currentInsertEnd = currentInsertStart; + } else { + currentInsertEnd++; + } + } + } + + result[i] = selectedModelLineNumber; + if (selectedModelLineNumber === modelLineCount) { + minModelLineNumber = selectedModelLineNumber; + } else { + minModelLineNumber = selectedModelLineNumber + 1; + } + } + + if (events.length < MAX_EVENT_COUNT) { + if (currentInsertEnd !== 0) { + // generate previous insert event + events.push({ type: 'inserted', insertFromLineNumber: currentInsertStart, insertToLineNumber: currentInsertEnd }); + oldDeltaLineCount += (currentInsertEnd - currentInsertStart + 1); + currentInsertStart = 0; + currentInsertEnd = 0; + } + while (oldIndex < oldLength) { + if (currentDeleteStart === 0) { + currentDeleteStart = oldIndex + 1 + oldDeltaLineCount; + currentDeleteEnd = currentDeleteStart; + } else { + currentDeleteEnd++; + } + oldIndex++; + } + if (currentDeleteEnd !== 0) { + // generate previous delete event + events.push({ type: 'deleted', deleteFromLineNumber: currentDeleteStart, deleteToLineNumber: currentDeleteEnd }); + oldDeltaLineCount -= (currentDeleteEnd - currentDeleteStart + 1); + currentDeleteStart = 0; + currentDeleteEnd = 0; + } + } else { + // too many events, just give up + events = [{ type: 'flush' }]; + } + + return [new MinimapSamplingState(true, ratio, result), events]; + } + + constructor( + public readonly isSampling: boolean, + public readonly samplingRatio: number, + public readonly minimapLines: number[] + ) { + } +} + export class Minimap extends ViewPart implements IMinimapModel { private _selections: Selection[]; @@ -517,11 +686,11 @@ export class Minimap extends ViewPart implements IMinimapModel { this._selections = []; this._minimapSelections = []; - this._isSampling = false; - this._samplingRatio = 1; - this._minimapLines = []; + const [samplingState,] = MinimapSamplingState.compute(this._context.configuration, this._context.model, null); + this._isSampling = samplingState.isSampling; + this._samplingRatio = samplingState.samplingRatio; + this._minimapLines = samplingState.minimapLines; this._shouldCheckSampling = false; - this._recreateLineSampling(true); this.tokensColorTracker = MinimapTokensColorTracker.getInstance(); this.options = new MinimapOptions(this._context.configuration, this._context.theme, this.tokensColorTracker, this._isSampling); @@ -552,7 +721,7 @@ export class Minimap extends ViewPart implements IMinimapModel { public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { let shouldRender = false; - shouldRender = this._recreateLineSampling(false) || shouldRender; + shouldRender = this._recreateLineSampling() || shouldRender; shouldRender = this._onOptionsMaybeChanged() || shouldRender; return shouldRender; } @@ -662,7 +831,7 @@ export class Minimap extends ViewPart implements IMinimapModel { public prepareRender(ctx: RenderingContext): void { if (this._shouldCheckSampling) { this._shouldCheckSampling = false; - this._recreateLineSampling(false); + this._recreateLineSampling(); } } @@ -696,165 +865,49 @@ export class Minimap extends ViewPart implements IMinimapModel { //#region IMinimapModel - private _createLineSampling(minimapLineCount: number, modelLineCount: number, ratio: number): number[] { - let result: number[] = []; - result[0] = 1; - if (minimapLineCount > 1) { - const halfRatio = ratio / 2; - for (let i = 0, lastIndex = minimapLineCount - 1; i < lastIndex; i++) { - result[i] = Math.round(i * ratio + halfRatio); - } - result[minimapLineCount - 1] = modelLineCount; - } - return result; - } - - private _recreateLineSampling(ctor: boolean): boolean { + private _recreateLineSampling(): boolean { const wasSampling = this._isSampling; - const options = this._context.configuration.options; - const minimapOpts = options.get(EditorOption.minimap); - if (!minimapOpts.entireDocument) { - // no sampling! - this._isSampling = false; - this._minimapLines = []; - this._samplingRatio = 1; - if (!ctor && wasSampling) { - // was sampling => sampling stopped - return this._onOptionsMaybeChanged(); - } - return false; - } + const [samplingState, events] = MinimapSamplingState.compute(this._context.configuration, this._context.model, this._minimapLines); + this._isSampling = samplingState.isSampling; + this._samplingRatio = samplingState.samplingRatio; + this._minimapLines = samplingState.minimapLines; - const layoutInfo = options.get(EditorOption.layoutInfo); - const pixelRatio = options.get(EditorOption.pixelRatio); - const lineHeight = options.get(EditorOption.lineHeight); - const scrollBeyondLastLine = options.get(EditorOption.scrollBeyondLastLine); - const modelLineCount = this._context.model.getLineCount(); - - const extraLinesBeyondLastLine = scrollBeyondLastLine ? (layoutInfo.height / lineHeight - 1) : 0; - const desiredRatio = (modelLineCount + extraLinesBeyondLastLine) / (pixelRatio * layoutInfo.height); - const minimapLineCount = Math.floor(modelLineCount / desiredRatio); - const ratio = modelLineCount / minimapLineCount; - - if (!this._minimapLines || this._minimapLines.length === 0) { - this._isSampling = true; - this._minimapLines = this._createLineSampling(minimapLineCount, modelLineCount, ratio); - this._samplingRatio = ratio; - if (!ctor && !wasSampling) { - // was not sampling => now is sampling - return this._onOptionsMaybeChanged(); - } - return false; - } - - const halfRatio = ratio / 2; - const oldMinimapLines = this._minimapLines; - const oldLength = oldMinimapLines.length; - let result: number[] = []; - let oldIndex = 0; - let oldDeltaLineCount = 0; - let minModelLineNumber = 1; - const MAX_EVENT_COUNT = 10; // generate at most 10 events, if there are more than 10 changes, just flush all previous data - let eventCount = 0; - let currentDeleteStart = 0, currentDeleteEnd = 0; - let currentInsertStart = 0, currentInsertEnd = 0; - for (let i = 0; i < minimapLineCount; i++) { - const fromModelLineNumber = Math.max(minModelLineNumber, Math.round(i * ratio)); - const toModelLineNumber = Math.max(fromModelLineNumber, Math.round((i + 1) * ratio)); - - while (oldIndex < oldLength && oldMinimapLines[oldIndex] < fromModelLineNumber) { - if (eventCount < MAX_EVENT_COUNT) { - if (currentInsertEnd !== 0) { - // must deliver previous insert event - this._actual.onLinesInserted(currentInsertStart, currentInsertEnd); - oldDeltaLineCount += (currentInsertEnd - currentInsertStart + 1); - currentInsertStart = 0; - currentInsertEnd = 0; - eventCount++; - } - - if (currentDeleteStart === 0) { - currentDeleteStart = oldIndex + 1 + oldDeltaLineCount; - currentDeleteEnd = currentDeleteStart; - } else { - currentDeleteEnd++; + let optionsMightHaveChanged: boolean; + if (wasSampling) { + if (this._isSampling) { + console.log(events); + // was sampling, is sampling + for (const event of events) { + switch(event.type) { + case 'deleted': + this._actual.onLinesDeleted(event.deleteFromLineNumber, event.deleteToLineNumber); + break; + case 'inserted': + this._actual.onLinesInserted(event.insertFromLineNumber, event.insertToLineNumber); + break; + case 'flush': + this._actual.onFlushed(); + break; } } - oldIndex++; - } - - let selectedModelLineNumber: number; - if (oldIndex < oldLength && oldMinimapLines[oldIndex] <= toModelLineNumber) { - // reuse the old sampled line - selectedModelLineNumber = oldMinimapLines[oldIndex]; - oldIndex++; + optionsMightHaveChanged = false; } else { - if (i === 0) { - selectedModelLineNumber = 1; - } else if (i + 1 === minimapLineCount) { - selectedModelLineNumber = modelLineCount; - } else { - selectedModelLineNumber = Math.round(i * ratio + halfRatio); - } - if (eventCount < MAX_EVENT_COUNT) { - if (currentDeleteEnd !== 0) { - // must deliver previous delete event - this._actual.onLinesDeleted(currentDeleteStart, currentDeleteEnd); - oldDeltaLineCount -= (currentDeleteEnd - currentDeleteStart + 1); - currentDeleteStart = 0; - currentDeleteEnd = 0; - eventCount++; - } - - if (currentInsertStart === 0) { - currentInsertStart = oldIndex + 1 + oldDeltaLineCount; - currentInsertEnd = currentInsertStart; - } else { - currentInsertEnd++; - } - } - } - - result[i] = selectedModelLineNumber; - if (selectedModelLineNumber === modelLineCount) { - minModelLineNumber = selectedModelLineNumber; - } else { - minModelLineNumber = selectedModelLineNumber + 1; - } - } - - if (eventCount < MAX_EVENT_COUNT) { - if (currentInsertEnd !== 0) { - // must deliver previous insert event - this._actual.onLinesInserted(currentInsertStart, currentInsertEnd); - oldDeltaLineCount += (currentInsertEnd - currentInsertStart + 1); - currentInsertStart = 0; - currentInsertEnd = 0; - } - while (oldIndex < oldLength) { - if (currentDeleteStart === 0) { - currentDeleteStart = oldIndex + 1 + oldDeltaLineCount; - currentDeleteEnd = currentDeleteStart; - } else { - currentDeleteEnd++; - } - oldIndex++; - } - if (currentDeleteEnd !== 0) { - // must deliver previous delete event - this._actual.onLinesDeleted(currentDeleteStart, currentDeleteEnd); - oldDeltaLineCount -= (currentDeleteEnd - currentDeleteStart + 1); - currentDeleteStart = 0; - currentDeleteEnd = 0; + // was not sampling, is sampling + optionsMightHaveChanged = true; } } else { - // too many events, just give up - this._actual.onFlushed(); + if (this._isSampling) { + // was not sampling, is sampling + optionsMightHaveChanged = true; + } else { + // was not sampling, is not sampling + optionsMightHaveChanged = false; + } } - this._isSampling = true; - this._samplingRatio = ratio; - this._minimapLines = result; + if (optionsMightHaveChanged) { + return this._onOptionsMaybeChanged(); + } return false; } From f82223f3186153336642cdc99f131134fecd0d75 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 13 Feb 2020 22:29:18 +0100 Subject: [PATCH 024/109] Fix event generation --- .../browser/viewParts/minimap/minimap.ts | 73 ++++++------------- 1 file changed, 22 insertions(+), 51 deletions(-) diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index 04218f80d2c8..a0acf222ab59 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -498,12 +498,14 @@ interface IMinimapRenderingContext { interface SamplingStateLinesDeletedEvent { type: 'deleted'; + _oldIndex: number; deleteFromLineNumber: number; deleteToLineNumber: number; } interface SamplingStateLinesInsertedEvent { type: 'inserted'; + _i: number; insertFromLineNumber: number; insertToLineNumber: number; } @@ -555,32 +557,22 @@ class MinimapSamplingState { let minModelLineNumber = 1; const MAX_EVENT_COUNT = 10; // generate at most 10 events, if there are more than 10 changes, just flush all previous data let events: SamplingStateEvent[] = []; - let currentDeleteStart = 0, currentDeleteEnd = 0; - let currentInsertStart = 0, currentInsertEnd = 0; + let lastEvent: SamplingStateEvent | null = null; for (let i = 0; i < minimapLineCount; i++) { const fromModelLineNumber = Math.max(minModelLineNumber, Math.round(i * ratio)); const toModelLineNumber = Math.max(fromModelLineNumber, Math.round((i + 1) * ratio)); - console.log(`LINE AT ${i} SHOULD FIT BTW ${fromModelLineNumber} to ${toModelLineNumber}`); - while (oldIndex < oldLength && oldMinimapLines[oldIndex] < fromModelLineNumber) { if (events.length < MAX_EVENT_COUNT) { - if (currentInsertEnd !== 0) { - // generate previous insert event - events.push({ type: 'inserted', insertFromLineNumber: currentInsertStart, insertToLineNumber: currentInsertEnd }); - oldDeltaLineCount += (currentInsertEnd - currentInsertStart + 1); - currentInsertStart = 0; - currentInsertEnd = 0; - } - - if (currentDeleteStart === 0) { - currentDeleteStart = oldIndex + 1 + oldDeltaLineCount; - currentDeleteEnd = currentDeleteStart; + const oldMinimapLineNumber = oldIndex + 1 + oldDeltaLineCount; + if (lastEvent && lastEvent.type === 'deleted' && lastEvent._oldIndex === oldIndex - 1) { + lastEvent.deleteToLineNumber++; } else { - currentDeleteEnd++; + lastEvent = { type: 'deleted', _oldIndex: oldIndex, deleteFromLineNumber: oldMinimapLineNumber, deleteToLineNumber: oldMinimapLineNumber }; + events.push(lastEvent); } + oldDeltaLineCount--; } - console.log(` => deleting ${oldMinimapLines[oldIndex]}`); oldIndex++; } @@ -588,7 +580,6 @@ class MinimapSamplingState { if (oldIndex < oldLength && oldMinimapLines[oldIndex] <= toModelLineNumber) { // reuse the old sampled line selectedModelLineNumber = oldMinimapLines[oldIndex]; - console.log(` => keeping ${oldMinimapLines[oldIndex]}`); oldIndex++; } else { if (i === 0) { @@ -598,22 +589,15 @@ class MinimapSamplingState { } else { selectedModelLineNumber = Math.round(i * ratio + halfRatio); } - console.log(` => inserting ${selectedModelLineNumber}`); if (events.length < MAX_EVENT_COUNT) { - if (currentDeleteEnd !== 0) { - // generate previous delete event - events.push({ type: 'deleted', deleteFromLineNumber: currentDeleteStart, deleteToLineNumber: currentDeleteEnd }); - oldDeltaLineCount -= (currentDeleteEnd - currentDeleteStart + 1); - currentDeleteStart = 0; - currentDeleteEnd = 0; - } - - if (currentInsertStart === 0) { - currentInsertStart = oldIndex + 1 + oldDeltaLineCount; - currentInsertEnd = currentInsertStart; + const oldMinimapLineNumber = oldIndex + 1 + oldDeltaLineCount; + if (lastEvent && lastEvent.type === 'inserted' && lastEvent._i === i - 1) { + lastEvent.insertToLineNumber++; } else { - currentInsertEnd++; + lastEvent = { type: 'inserted', _i: i, insertFromLineNumber: oldMinimapLineNumber, insertToLineNumber: oldMinimapLineNumber }; + events.push(lastEvent); } + oldDeltaLineCount++; } } @@ -626,29 +610,17 @@ class MinimapSamplingState { } if (events.length < MAX_EVENT_COUNT) { - if (currentInsertEnd !== 0) { - // generate previous insert event - events.push({ type: 'inserted', insertFromLineNumber: currentInsertStart, insertToLineNumber: currentInsertEnd }); - oldDeltaLineCount += (currentInsertEnd - currentInsertStart + 1); - currentInsertStart = 0; - currentInsertEnd = 0; - } while (oldIndex < oldLength) { - if (currentDeleteStart === 0) { - currentDeleteStart = oldIndex + 1 + oldDeltaLineCount; - currentDeleteEnd = currentDeleteStart; + const oldMinimapLineNumber = oldIndex + 1 + oldDeltaLineCount; + if (lastEvent && lastEvent.type === 'deleted' && lastEvent._oldIndex === oldIndex - 1) { + lastEvent.deleteToLineNumber++; } else { - currentDeleteEnd++; + lastEvent = { type: 'deleted', _oldIndex: oldIndex, deleteFromLineNumber: oldMinimapLineNumber, deleteToLineNumber: oldMinimapLineNumber }; + events.push(lastEvent); } + oldDeltaLineCount--; oldIndex++; } - if (currentDeleteEnd !== 0) { - // generate previous delete event - events.push({ type: 'deleted', deleteFromLineNumber: currentDeleteStart, deleteToLineNumber: currentDeleteEnd }); - oldDeltaLineCount -= (currentDeleteEnd - currentDeleteStart + 1); - currentDeleteStart = 0; - currentDeleteEnd = 0; - } } else { // too many events, just give up events = [{ type: 'flush' }]; @@ -875,10 +847,9 @@ export class Minimap extends ViewPart implements IMinimapModel { let optionsMightHaveChanged: boolean; if (wasSampling) { if (this._isSampling) { - console.log(events); // was sampling, is sampling for (const event of events) { - switch(event.type) { + switch (event.type) { case 'deleted': this._actual.onLinesDeleted(event.deleteFromLineNumber, event.deleteToLineNumber); break; From 799be872a07bc1abfea69dd9f4ba926eb4ac6837 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 14 Feb 2020 00:37:13 +0100 Subject: [PATCH 025/109] Cover case when rendering entire document and there are few lines --- .../browser/viewParts/minimap/minimap.ts | 83 +++++++++++++------ 1 file changed, 59 insertions(+), 24 deletions(-) diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index a0acf222ab59..3e0a325df4c2 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -48,6 +48,8 @@ class MinimapOptions { public readonly renderMinimap: RenderMinimap; + public readonly entireDocument: boolean; + public readonly scrollBeyondLastLine: boolean; public readonly showSlider: 'always' | 'mouseover'; @@ -58,10 +60,6 @@ class MinimapOptions { public readonly lineHeight: number; - public readonly fontScale: number; - - public readonly charRenderer: () => MinimapCharRenderer; - /** * container dom node left position (in CSS px) */ @@ -95,22 +93,23 @@ class MinimapOptions { public readonly backgroundColor: RGBA8; + public readonly fontScale: number; public readonly minimapLineHeight: number; public readonly minimapCharWidth: number; + public readonly charRenderer: () => MinimapCharRenderer; - constructor(configuration: IConfiguration, theme: EditorTheme, tokensColorTracker: MinimapTokensColorTracker, isSampling: boolean) { + constructor(configuration: IConfiguration, theme: EditorTheme, tokensColorTracker: MinimapTokensColorTracker, modelLineCount: number, isSampling: boolean) { const options = configuration.options; const pixelRatio = options.get(EditorOption.pixelRatio); const layoutInfo = options.get(EditorOption.layoutInfo); const fontInfo = options.get(EditorOption.fontInfo); + const minimapOpts = options.get(EditorOption.minimap); this.isSampling = isSampling; this.renderMinimap = layoutInfo.renderMinimap | 0; + this.entireDocument = minimapOpts.entireDocument; this.scrollBeyondLastLine = options.get(EditorOption.scrollBeyondLastLine); - const minimapOpts = options.get(EditorOption.minimap); this.showSlider = minimapOpts.showSlider; - this.fontScale = (isSampling ? 1 : Math.round(minimapOpts.scale * pixelRatio)); - this.charRenderer = once(() => MinimapCharRendererFactory.create(this.fontScale, fontInfo.fontFamily)); this.pixelRatio = pixelRatio; this.typicalHalfwidthCharacterWidth = fontInfo.typicalHalfwidthCharacterWidth; this.lineHeight = options.get(EditorOption.lineHeight); @@ -126,9 +125,32 @@ class MinimapOptions { this.backgroundColor = MinimapOptions._getMinimapBackground(theme, tokensColorTracker); - const baseCharHeight = (isSampling ? 1 : this.renderMinimap === RenderMinimap.Text ? Constants.BASE_CHAR_HEIGHT : Constants.BASE_CHAR_HEIGHT + 1); - this.minimapLineHeight = baseCharHeight * this.fontScale; + const baseCharHeight = (this.renderMinimap === RenderMinimap.Text ? Constants.BASE_CHAR_HEIGHT : Constants.BASE_CHAR_HEIGHT + 1); + + let fontScale: number; + let minimapLineHeight: number; + if (minimapOpts.entireDocument) { + if (isSampling) { + fontScale = 1; + minimapLineHeight = 1; + } else { + const extraLinesBeyondLastLine = this.scrollBeyondLastLine ? (layoutInfo.height / this.lineHeight - 1) : 0; + const desiredRatio = (modelLineCount + extraLinesBeyondLastLine) / (pixelRatio * layoutInfo.height); + minimapLineHeight = Math.max(1, Math.floor(1 / desiredRatio)); + // fontScale = Math.round(minimapOpts.scale * pixelRatio); + fontScale = Math.max(1, Math.floor(minimapLineHeight / baseCharHeight / 2)); + const actualMinimapHeight = (modelLineCount + extraLinesBeyondLastLine) * minimapLineHeight; + this.canvasInnerHeight = Math.ceil(actualMinimapHeight); + } + } else { + fontScale = Math.round(minimapOpts.scale * pixelRatio); + minimapLineHeight = baseCharHeight * fontScale; + } + + this.fontScale = fontScale; + this.minimapLineHeight = minimapLineHeight; this.minimapCharWidth = Constants.BASE_CHAR_WIDTH * this.fontScale; + this.charRenderer = once(() => MinimapCharRendererFactory.create(this.fontScale, fontInfo.fontFamily)); } private static _getMinimapBackground(theme: EditorTheme, tokensColorTracker: MinimapTokensColorTracker): RGBA8 { @@ -142,6 +164,7 @@ class MinimapOptions { public equals(other: MinimapOptions): boolean { return (this.isSampling === other.isSampling && this.renderMinimap === other.renderMinimap + && this.entireDocument === other.entireDocument && this.scrollBeyondLastLine === other.scrollBeyondLastLine && this.showSlider === other.showSlider && this.pixelRatio === other.pixelRatio @@ -224,12 +247,12 @@ class MinimapLayout { public static create( options: MinimapOptions, - samplingRatio: number, viewportStartLineNumber: number, viewportEndLineNumber: number, viewportHeight: number, viewportContainsWhitespaceGaps: boolean, lineCount: number, + realLineCount: number, scrollTop: number, scrollHeight: number, previousLayout: MinimapLayout | null @@ -239,9 +262,12 @@ class MinimapLayout { const minimapLinesFitting = Math.floor(options.canvasInnerHeight / minimapLineHeight); const lineHeight = options.lineHeight; - if (options.isSampling) { - const expectedViewportLineCount = viewportHeight / lineHeight; - const sliderHeight = Math.max(1, Math.floor(expectedViewportLineCount * minimapLineHeight / pixelRatio / samplingRatio)); + if (options.entireDocument) { + const logicalScrollHeight = ( + realLineCount * options.lineHeight + + (options.scrollBeyondLastLine ? viewportHeight - options.lineHeight : 0) + ); + const sliderHeight = Math.max(1, Math.floor(viewportHeight * viewportHeight / logicalScrollHeight)); const maxMinimapSliderTop = Math.max(0, options.minimapHeight - sliderHeight); // The slider can move from 0 to `maxMinimapSliderTop` // in the same way `scrollTop` can move from 0 to `scrollHeight` - `viewportHeight`. @@ -470,6 +496,7 @@ export interface IMinimapModel { readonly options: MinimapOptions; getLineCount(): number; + getRealLineCount(): number; getLineContent(lineNumber: number): string; getMinimapLinesRenderingData(startLineNumber: number, endLineNumber: number, needed: boolean[]): (ViewLineData | null)[]; getSelections(): Selection[]; @@ -480,7 +507,6 @@ export interface IMinimapModel { } interface IMinimapRenderingContext { - readonly samplingRatio: number; readonly viewportContainsWhitespaceGaps: boolean; readonly scrollWidth: number; @@ -536,6 +562,10 @@ class MinimapSamplingState { const minimapLineCount = Math.floor(modelLineCount / desiredRatio); const ratio = modelLineCount / minimapLineCount; + if (ratio <= 1) { + return [new MinimapSamplingState(false, 1, []), []]; + } + if (!oldMinimapLines || oldMinimapLines.length === 0) { let result: number[] = []; result[0] = 1; @@ -602,11 +632,7 @@ class MinimapSamplingState { } result[i] = selectedModelLineNumber; - if (selectedModelLineNumber === modelLineCount) { - minModelLineNumber = selectedModelLineNumber; - } else { - minModelLineNumber = selectedModelLineNumber + 1; - } + minModelLineNumber = selectedModelLineNumber; } if (events.length < MAX_EVENT_COUNT) { @@ -665,7 +691,7 @@ export class Minimap extends ViewPart implements IMinimapModel { this._shouldCheckSampling = false; this.tokensColorTracker = MinimapTokensColorTracker.getInstance(); - this.options = new MinimapOptions(this._context.configuration, this._context.theme, this.tokensColorTracker, this._isSampling); + this.options = new MinimapOptions(this._context.configuration, this._context.theme, this.tokensColorTracker, this._context.model.getLineCount(), this._isSampling); this._actual = new InnerMinimap(context.theme, this); } @@ -680,7 +706,7 @@ export class Minimap extends ViewPart implements IMinimapModel { } private _onOptionsMaybeChanged(): boolean { - const opts = new MinimapOptions(this._context.configuration, this._context.theme, this.tokensColorTracker, this._isSampling); + const opts = new MinimapOptions(this._context.configuration, this._context.theme, this.tokensColorTracker, this._context.model.getLineCount(), this._isSampling); if (this.options.equals(opts)) { return false; } @@ -745,6 +771,9 @@ export class Minimap extends ViewPart implements IMinimapModel { this._shouldCheckSampling = true; return true; } else { + if (this.options.entireDocument && this._onOptionsMaybeChanged()) { + return true; + } return this._actual.onLinesDeleted(e.fromLineNumber, e.toLineNumber); } } @@ -761,6 +790,9 @@ export class Minimap extends ViewPart implements IMinimapModel { this._shouldCheckSampling = true; return true; } else { + if (this.options.entireDocument && this._onOptionsMaybeChanged()) { + return true; + } return this._actual.onLinesInserted(e.fromLineNumber, e.toLineNumber); } } @@ -817,7 +849,6 @@ export class Minimap extends ViewPart implements IMinimapModel { } const minimapCtx: IMinimapRenderingContext = { - samplingRatio: this._samplingRatio, viewportContainsWhitespaceGaps: (ctx.viewportData.whitespaceViewportData.length > 0), scrollWidth: ctx.scrollWidth, @@ -940,6 +971,10 @@ export class Minimap extends ViewPart implements IMinimapModel { return this._context.model.getLineCount(); } + public getRealLineCount(): number { + return this._context.model.getLineCount(); + } + public getLineContent(lineNumber: number): string { if (this._isSampling) { return this._context.model.getLineContent(this._minimapLines[lineNumber - 1]); @@ -1317,12 +1352,12 @@ class InnerMinimap extends Disposable { const layout = MinimapLayout.create( this._model.options, - renderingCtx.samplingRatio, renderingCtx.viewportStartLineNumber, renderingCtx.viewportEndLineNumber, renderingCtx.viewportHeight, renderingCtx.viewportContainsWhitespaceGaps, this._model.getLineCount(), + this._model.getRealLineCount(), renderingCtx.scrollTop, renderingCtx.scrollHeight, this._lastRenderData ? this._lastRenderData.renderedLayout : null From e2bc02523e999d9273405d9592517a00df5d99fe Mon Sep 17 00:00:00 2001 From: Rachel Macfarlane Date: Thu, 13 Feb 2020 16:51:22 -0800 Subject: [PATCH 026/109] Properly await setting the active settings sync account, fixes #90636 --- .../contrib/userDataSync/browser/userDataSync.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 5e756cd3704a..6af68ea3febd 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -147,13 +147,13 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } if (sessions.length === 0) { - this.setActiveAccount(undefined); + await this.setActiveAccount(undefined); return; } if (sessions.length === 1) { this.logAuthenticatedEvent(sessions[0]); - this.setActiveAccount(sessions[0]); + await this.setActiveAccount(sessions[0]); return; } @@ -167,7 +167,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo if (selectedAccount) { const selected = sessions.filter(account => selectedAccount.id === account.id)[0]; this.logAuthenticatedEvent(selected); - this.setActiveAccount(selected); + await this.setActiveAccount(selected); } } @@ -565,7 +565,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo private async signIn(): Promise { try { - this.setActiveAccount(await this.authenticationService.login(this.userDataSyncStore!.authenticationProviderId, ['https://management.core.windows.net/.default', 'offline_access'])); + await this.setActiveAccount(await this.authenticationService.login(this.userDataSyncStore!.authenticationProviderId, ['https://management.core.windows.net/.default', 'offline_access'])); } catch (e) { this.notificationService.error(e); throw e; @@ -575,7 +575,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo private async signOut(): Promise { if (this.activeAccount) { await this.authenticationService.logout(this.userDataSyncStore!.authenticationProviderId, this.activeAccount.id); - this.setActiveAccount(undefined); + await this.setActiveAccount(undefined); } } From 4c9161a3f125f5a788aafa73a4ecfcb27dcfc319 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Thu, 13 Feb 2020 17:22:19 -0800 Subject: [PATCH 027/109] Remove unused action --- .../browser/searchEditor.contribution.ts | 6 +----- .../browser/searchEditorActions.ts | 19 ------------------- 2 files changed, 1 insertion(+), 24 deletions(-) diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts index 653cf67456be..1884a02ec443 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts @@ -26,7 +26,7 @@ import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileE import * as SearchConstants from 'vs/workbench/contrib/search/common/constants'; import * as SearchEditorConstants from 'vs/workbench/contrib/searchEditor/browser/constants'; import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor'; -import { OpenResultsInEditorAction, OpenSearchEditorAction, ReRunSearchEditorSearchAction, toggleSearchEditorCaseSensitiveCommand, toggleSearchEditorContextLinesCommand, toggleSearchEditorRegexCommand, toggleSearchEditorWholeWordCommand } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions'; +import { OpenResultsInEditorAction, OpenSearchEditorAction, toggleSearchEditorCaseSensitiveCommand, toggleSearchEditorContextLinesCommand, toggleSearchEditorRegexCommand, toggleSearchEditorWholeWordCommand } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions'; import { getOrMakeSearchEditorInput, SearchEditorInput } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search'; @@ -164,10 +164,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ const registry = Registry.as(ActionExtensions.WorkbenchActions); const category = localize('search', "Search Editor"); -registry.registerWorkbenchAction( - SyncActionDescriptor.create(ReRunSearchEditorSearchAction, ReRunSearchEditorSearchAction.ID, ReRunSearchEditorSearchAction.LABEL), - 'Search Editor: Rerun search', category, ContextKeyExpr.and(SearchEditorConstants.InSearchEditor)); - registry.registerWorkbenchAction( SyncActionDescriptor.create(OpenResultsInEditorAction, OpenResultsInEditorAction.ID, OpenResultsInEditorAction.LABEL, { mac: { primary: KeyMod.CtrlCmd | KeyCode.Enter } }, diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts index cabfbd9a6ca0..e07b696c2cf2 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts @@ -112,25 +112,6 @@ export class OpenResultsInEditorAction extends Action { } } - -export class ReRunSearchEditorSearchAction extends Action { - - static readonly ID = 'searchEditor.rerunSerach'; - static readonly LABEL = localize('search.rerunSearch', "Rerun Search in Editor"); - - constructor(id: string, label: string, - @IEditorService private readonly editorService: IEditorService) { - super(id, label); - } - - async run() { - const input = this.editorService.activeEditor; - if (input instanceof SearchEditorInput) { - await (this.editorService.activeControl as SearchEditor).runSearch(false, true); - } - } -} - const openNewSearchEditor = async (accessor: ServicesAccessor) => { const editorService = accessor.get(IEditorService); From b62b2280ea0ea828b898df94c43a455194639218 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 14 Feb 2020 07:24:17 +0100 Subject: [PATCH 028/109] fixes #90620 --- extensions/git/package.nls.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index f3d755196534..f51154cebf69 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -127,7 +127,7 @@ "config.showProgress": "Controls whether git actions should show progress.", "config.rebaseWhenSync": "Force git to use rebase when running the sync command.", "config.confirmEmptyCommits": "Always confirm the creation of empty commits for the 'Git: Commit Empty' command.", - "config.fetchOnPull": "Fetch all branches when pulling or just the current one.", + "config.fetchOnPull": "When enabled, fetch all branches when pulling. Otherwise, fetch just the current one.", "config.pullTags": "Fetch all tags when pulling.", "config.autoStash": "Stash any changes before pulling and restore them after successful pull.", "config.allowForcePush": "Controls whether force push (with or without lease) is enabled.", From 7a8596fd4b6039c7536f351953a2aede526f8429 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 14 Feb 2020 09:26:14 +0100 Subject: [PATCH 029/109] text files - rewrite save to use async-await :crossed_fingers: --- .../textfile/common/textFileEditorModel.ts | 112 ++++++++++-------- .../common/textFileEditorModelManager.ts | 8 +- .../test/browser/textFileEditorModel.test.ts | 3 - 3 files changed, 67 insertions(+), 56 deletions(-) diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index fb425e6ac9f3..15c92a28b1bb 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -79,7 +79,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil private lastResolvedFileStat: IFileStatWithMetadata | undefined; private readonly saveSequentializer = new TaskSequentializer(); - private lastSaveAttemptTime = 0; private dirty = false; private inConflictMode = false; @@ -553,16 +552,15 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil return false; // if model is in save conflict or error, do not save unless save reason is explicit } + // Actually do save and log this.logService.trace('[text file model] save() - enter', this.resource.toString()); - await this.doSave(options); - this.logService.trace('[text file model] save() - exit', this.resource.toString()); return true; } - private doSave(options: ITextFileSaveOptions): Promise { + private async doSave(options: ITextFileSaveOptions): Promise { if (typeof options.reason !== 'number') { options.reason = SaveReason.EXPLICIT; } @@ -587,7 +585,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil if (!options.force && !this.dirty) { this.logService.trace(`[text file model] doSave(${versionId}) - exit - because not dirty and/or versionId is different (this.isDirty: ${this.dirty}, this.versionId: ${this.versionId})`, this.resource.toString()); - return Promise.resolve(); + return; } // Return if currently saving by storing this save request as the next save that should happen. @@ -618,26 +616,26 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.textEditorModel.pushStackElement(); } - // A save participant can still change the model now and since we are so close to saving - // we do not want to trigger another auto save or similar, so we block this - // In addition we update our version right after in case it changed because of a model change - // - // Save participants can also be skipped through API. const saveParticipantCancellation = new CancellationTokenSource(); - let saveParticipantPromise: Promise = Promise.resolve(versionId); - if (this.isResolved() && this.textFileService.saveParticipant && !options.skipSaveParticipants) { - const onCompleteOrError = () => { - this.ignoreDirtyOnModelContentChange = false; - return this.versionId; - }; + return this.saveSequentializer.setPending(versionId, (async () => { - this.ignoreDirtyOnModelContentChange = true; - saveParticipantPromise = this.textFileService.saveParticipant.participate(this, { reason: options.reason }, saveParticipantCancellation.token).then(onCompleteOrError, onCompleteOrError); - } + // A save participant can still change the model now and since we are so close to saving + // we do not want to trigger another auto save or similar, so we block this + // In addition we update our version right after in case it changed because of a model change + // + // Save participants can also be skipped through API. + if (this.isResolved() && this.textFileService.saveParticipant && !options.skipSaveParticipants) { + try { + this.ignoreDirtyOnModelContentChange = true; - // mark the save participant as current pending save operation - return this.saveSequentializer.setPending(versionId, saveParticipantPromise.then(newVersionId => { + await this.textFileService.saveParticipant.participate(this, { reason: options.reason ?? SaveReason.EXPLICIT }, saveParticipantCancellation.token); + } catch (error) { + // Ignore + } finally { + this.ignoreDirtyOnModelContentChange = false; + } + } // We have to protect against being disposed at this point. It could be that the save() operation // was triggerd followed by a dispose() operation right after without waiting. Typically we cannot @@ -661,32 +659,41 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // - the model is not dirty (otherwise we know there are changed which needs to go to the file) // - the model is not in orphan mode (because in that case we know the file does not exist on disk) // - the model version did not change due to save participants running - if (options.force && !this.dirty && !this.inOrphanMode && options.reason === SaveReason.EXPLICIT && versionId === newVersionId) { - return this.doTouch(newVersionId, options.reason); + if (options.force && !this.dirty && !this.inOrphanMode && options.reason === SaveReason.EXPLICIT && versionId === this.versionId) { + return this.doTouch(this.versionId, options.reason); } // update versionId with its new value (if pre-save changes happened) - versionId = newVersionId; + versionId = this.versionId; // Clear error flag since we are trying to save again this.inErrorMode = false; - // Remember when this model was saved last - this.lastSaveAttemptTime = Date.now(); - - // Save to Disk - // mark the save operation as currently pending with the versionId (it might have changed from a save participant triggering) + // Save to Disk. We mark the save operation as currently pending with + // the latest versionId because it might have changed from a save + // participant triggering this.logService.trace(`[text file model] doSave(${versionId}) - before write()`, this.resource.toString()); const lastResolvedFileStat = assertIsDefined(this.lastResolvedFileStat); - return this.saveSequentializer.setPending(newVersionId, this.textFileService.write(lastResolvedFileStat.resource, this.createSnapshot(), { - overwriteReadonly: options.overwriteReadonly, - overwriteEncoding: options.overwriteEncoding, - mtime: lastResolvedFileStat.mtime, - encoding: this.getEncoding(), - etag: (options.ignoreModifiedSince || !this.filesConfigurationService.preventSaveConflicts(lastResolvedFileStat.resource, this.getMode())) ? ETAG_DISABLED : lastResolvedFileStat.etag, - writeElevated: options.writeElevated - }).then(stat => this.handleSaveSuccess(stat, versionId, options), error => this.handleSaveError(error, versionId, options))); - }), () => saveParticipantCancellation.cancel()); + + const textFileEdiorModel = this; + + return this.saveSequentializer.setPending(versionId, (async () => { + try { + const stat = await this.textFileService.write(lastResolvedFileStat.resource, textFileEdiorModel.createSnapshot(), { + overwriteReadonly: options.overwriteReadonly, + overwriteEncoding: options.overwriteEncoding, + mtime: lastResolvedFileStat.mtime, + encoding: this.getEncoding(), + etag: (options.ignoreModifiedSince || !this.filesConfigurationService.preventSaveConflicts(lastResolvedFileStat.resource, textFileEdiorModel.getMode())) ? ETAG_DISABLED : lastResolvedFileStat.etag, + writeElevated: options.writeElevated + }); + + this.handleSaveSuccess(stat, versionId, options); + } catch (error) { + this.handleSaveError(error, versionId, options); + } + })()); + })(), () => saveParticipantCancellation.cancel()); } private handleSaveSuccess(stat: IFileStatWithMetadata, versionId: number, options: ITextFileSaveOptions): void { @@ -733,19 +740,24 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil private doTouch(this: TextFileEditorModel & IResolvedTextFileEditorModel, versionId: number, reason: SaveReason): Promise { const lastResolvedFileStat = assertIsDefined(this.lastResolvedFileStat); - return this.saveSequentializer.setPending(versionId, this.textFileService.write(lastResolvedFileStat.resource, this.createSnapshot(), { - mtime: lastResolvedFileStat.mtime, - encoding: this.getEncoding(), - etag: lastResolvedFileStat.etag - }).then(stat => { - // Updated resolved stat with updated stat since touching it might have changed mtime - this.updateLastResolvedFileStat(stat); + return this.saveSequentializer.setPending(versionId, (async () => { + try { + const stat = await this.textFileService.write(lastResolvedFileStat.resource, this.createSnapshot(), { + mtime: lastResolvedFileStat.mtime, + encoding: this.getEncoding(), + etag: lastResolvedFileStat.etag + }); - // Emit File Saved Event - this._onDidSave.fire(reason); + // Updated resolved stat with updated stat since touching it might have changed mtime + this.updateLastResolvedFileStat(stat); - }, error => onUnexpectedError(error) /* just log any error but do not notify the user since the file was not dirty */)); + // Emit File Saved Event + this._onDidSave.fire(reason); + } catch (error) { + onUnexpectedError(error); // just log any error but do not notify the user since the file was not dirty + } + })()); } private updateSavedVersionId(): void { @@ -776,10 +788,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil //#endregion - getLastSaveAttemptTime(): number { - return this.lastSaveAttemptTime; - } - hasState(state: ModelState): boolean { switch (state) { case ModelState.CONFLICT: diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts index 337797b264b1..a4fbde9a277a 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts @@ -84,7 +84,13 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE // to have a size of 2 (1 running load and 1 queued load). const queue = this.modelLoadQueue.queueFor(model.resource); if (queue.size <= 1) { - queue.queue(() => model.load().then(undefined, onUnexpectedError)); + queue.queue(async () => { + try { + await model.load(); + } catch (error) { + onUnexpectedError(error); + } + }); } } diff --git a/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts b/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts index 61bddf39309e..aff82ec1b835 100644 --- a/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts +++ b/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts @@ -120,7 +120,6 @@ suite('Files - TextFileEditorModel', () => { await pendingSave; - assert.ok(model.getLastSaveAttemptTime() <= Date.now()); assert.ok(model.hasState(ModelState.SAVED)); assert.ok(!model.isDirty()); assert.ok(savedEvent); @@ -488,8 +487,6 @@ suite('Files - TextFileEditorModel', () => { assert.ok(!accessor.textFileService.isDirty(toResource.call(this, '/path/index_async2.txt'))); assert.ok(assertIsDefined(model1.getStat()).mtime > m1Mtime); assert.ok(assertIsDefined(model2.getStat()).mtime > m2Mtime); - assert.ok(model1.getLastSaveAttemptTime() > m1Mtime); - assert.ok(model2.getLastSaveAttemptTime() > m2Mtime); model1.dispose(); model2.dispose(); From 9cda3b7bda1d4cfa851760e0077667f8068f36ab Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 14 Feb 2020 09:31:20 +0100 Subject: [PATCH 030/109] debt - do not ignore dirty when invoking save participants if a save participant changes the model and the save fails, the document is not marked as dirty --- .../workbench/services/textfile/common/textFileEditorModel.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index 15c92a28b1bb..d5957f7c7d40 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -627,13 +627,9 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Save participants can also be skipped through API. if (this.isResolved() && this.textFileService.saveParticipant && !options.skipSaveParticipants) { try { - this.ignoreDirtyOnModelContentChange = true; - await this.textFileService.saveParticipant.participate(this, { reason: options.reason ?? SaveReason.EXPLICIT }, saveParticipantCancellation.token); } catch (error) { // Ignore - } finally { - this.ignoreDirtyOnModelContentChange = false; } } From a7ed9c9572f51eee964b5d290ac0d170d9a01439 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 14 Feb 2020 09:56:08 +0100 Subject: [PATCH 031/109] :up: playwright@0.11.0 (#90663) --- .../src/singlefolder-tests/window.test.ts | 8 +---- package.json | 2 +- test/automation/src/playwrightDriver.ts | 8 +++-- test/integration/browser/package.json | 1 + test/integration/browser/src/index.ts | 10 +++--- test/integration/browser/yarn.lock | 5 +++ test/unit/browser/index.js | 3 +- yarn.lock | 33 ++++++++++--------- 8 files changed, 38 insertions(+), 32 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts index a8fef8ec2adc..620ce762632e 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { workspace, window, commands, ViewColumn, TextEditorViewColumnChangeEvent, Uri, Selection, Position, CancellationTokenSource, TextEditorSelectionChangeKind, env, UIKind } from 'vscode'; +import { workspace, window, commands, ViewColumn, TextEditorViewColumnChangeEvent, Uri, Selection, Position, CancellationTokenSource, TextEditorSelectionChangeKind } from 'vscode'; import { join } from 'path'; import { closeAllEditors, pathEquals, createRandomFile } from '../utils'; @@ -146,12 +146,6 @@ suite('window namespace tests', () => { }); test('active editor not always correct... #49125', async function () { - if (env.uiKind === UIKind.Web) { - // https://github.com/microsoft/vscode/issues/90470 - // https://github.com/microsoft/playwright/issues/979 - this.skip(); - return; - } const randomFile1 = await createRandomFile(); const randomFile2 = await createRandomFile(); diff --git a/package.json b/package.json index 531ea49e514a..e8d355e18cd7 100644 --- a/package.json +++ b/package.json @@ -139,7 +139,7 @@ "opn": "^6.0.0", "optimist": "0.3.5", "p-all": "^1.0.0", - "playwright": "^0.10.0", + "playwright": "0.11.0", "pump": "^1.0.1", "queue": "3.0.6", "rcedit": "^1.1.0", diff --git a/test/automation/src/playwrightDriver.ts b/test/automation/src/playwrightDriver.ts index c18e01808d3b..40ee4c82851e 100644 --- a/test/automation/src/playwrightDriver.ts +++ b/test/automation/src/playwrightDriver.ts @@ -141,10 +141,12 @@ export function connect(engine: 'chromium' | 'webkit' | 'firefox' = 'chromium'): return new Promise(async (c) => { const browser = await playwright[engine].launch({ // Run in Edge dev on macOS - // executablePath: '/Applications/Microsoft\ Edge\ Dev.app/Contents/MacOS/Microsoft\ Edge\ Dev' + // executablePath: '/Applications/Microsoft\ Edge\ Dev.app/Contents/MacOS/Microsoft\ Edge\ Dev', + headless: false }); - const page = (await browser.defaultContext().pages())[0]; - await page.setViewport({ width, height }); + const context = await browser.newContext(); + const page = await context.newPage(); + await page.setViewportSize({ width, height }); await page.goto(`${endpoint}&folder=vscode-remote://localhost:9888${URI.file(workspacePath!).path}`); const result = { client: { dispose: () => browser.close() && teardown() }, diff --git a/test/integration/browser/package.json b/test/integration/browser/package.json index d43ee230c465..beb4142c55dd 100644 --- a/test/integration/browser/package.json +++ b/test/integration/browser/package.json @@ -9,6 +9,7 @@ "devDependencies": { "@types/mkdirp": "0.5.1", "@types/node": "^12.11.7", + "@types/optimist": "0.0.29", "@types/rimraf": "2.0.2", "@types/tmp": "^0.1.0", "rimraf": "^2.6.1", diff --git a/test/integration/browser/src/index.ts b/test/integration/browser/src/index.ts index 2ed75099d31f..7972dd765216 100644 --- a/test/integration/browser/src/index.ts +++ b/test/integration/browser/src/index.ts @@ -11,8 +11,9 @@ import * as tmp from 'tmp'; import * as rimraf from 'rimraf'; import { URI } from 'vscode-uri'; import * as kill from 'tree-kill'; +import * as optimistLib from 'optimist'; -const optimist = require('optimist') +const optimist = optimistLib .describe('workspacePath', 'path to the workspace to open in the test').string('workspacePath') .describe('extensionDevelopmentPath', 'path to the extension to test').string('extensionDevelopmentPath') .describe('extensionTestsPath', 'path to the extension tests').string('extensionTestsPath') @@ -28,11 +29,12 @@ if (optimist.argv.help) { const width = 1200; const height = 800; -async function runTestsInBrowser(browserType: string, endpoint: url.UrlWithStringQuery, server: cp.ChildProcess): Promise { +async function runTestsInBrowser(browserType: 'chromium' | 'firefox' | 'webkit', endpoint: url.UrlWithStringQuery, server: cp.ChildProcess): Promise { const args = process.platform === 'linux' && browserType === 'chromium' ? ['--no-sandbox'] : undefined; // disable sandbox to run chrome on certain Linux distros const browser = await playwright[browserType].launch({ headless: !Boolean(optimist.argv.debug), dumpio: true, args }); - const page = (await browser.defaultContext().pages())[0]; - await page.setViewport({ width, height }); + const context = await browser.newContext(); + const page = await context.newPage(); + await page.setViewportSize({ width, height }); const host = endpoint.host; const protocol = 'vscode-remote'; diff --git a/test/integration/browser/yarn.lock b/test/integration/browser/yarn.lock index 35884dcfc513..126da8138c5d 100644 --- a/test/integration/browser/yarn.lock +++ b/test/integration/browser/yarn.lock @@ -38,6 +38,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.26.tgz#213e153babac0ed169d44a6d919501e68f59dea9" integrity sha512-UmUm94/QZvU5xLcUlNR8hA7Ac+fGpO1EG/a8bcWVz0P0LqtxFmun9Y2bbtuckwGboWJIT70DoWq1r3hb56n3DA== +"@types/optimist@0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/optimist/-/optimist-0.0.29.tgz#a8873580b3a84b69ac1e687323b15fbbeb90479a" + integrity sha1-qIc1gLOoS2msHmhzI7Ffu+uQR5o= + "@types/rimraf@2.0.2": version "2.0.2" resolved "https://registry.yarnpkg.com/@types/rimraf/-/rimraf-2.0.2.tgz#7f0fc3cf0ff0ad2a99bb723ae1764f30acaf8b6e" diff --git a/test/unit/browser/index.js b/test/unit/browser/index.js index 884cb5de6673..d6e15c5e28df 100644 --- a/test/unit/browser/index.js +++ b/test/unit/browser/index.js @@ -120,7 +120,8 @@ const testModules = (async function () { async function runTestsInBrowser(testModules, browserType) { const args = process.platform === 'linux' && browserType === 'chromium' ? ['--no-sandbox'] : undefined; // disable sandbox to run chrome on certain Linux distros const browser = await playwright[browserType].launch({ headless: !Boolean(argv.debug), dumpio: true, args }); - const page = (await browser.defaultContext().pages())[0] + const context = await browser.newContext(); + const page = await context.newPage(); const target = url.pathToFileURL(path.join(__dirname, 'renderer.html')); if (argv.build) { target.search = `?build=true`; diff --git a/yarn.lock b/yarn.lock index e55bdc53b9f8..e4f5accd3c77 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5878,11 +5878,6 @@ mime@^1.4.1: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== -mime@^2.0.3: - version "2.4.4" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.4.tgz#bd7b91135fc6b01cde3e9bae33d659b63d8857e5" - integrity sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA== - mimic-fn@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" @@ -6927,29 +6922,28 @@ pkg-dir@^3.0.0: dependencies: find-up "^3.0.0" -playwright-core@=0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-0.10.0.tgz#86699c9cc3e613d733e6635a54aceea1993013d5" - integrity sha512-yernA6yrrBhmb8M5eO6GZsJOrBKWOZszlu65Luz8LP7ryaDExN1sE9XjQBNbiwJ5Gfs8cehtAO7GfTDJt+Z2cQ== +playwright-core@=0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-0.11.0.tgz#a2372833f6ec4e7886c4409e3da93df997aee61b" + integrity sha512-9UPP/Max65PMiZJz9DNWB3ZRWtTlYlceLFnm6JO8aU7m6Vw3gwCvuSGoC5W69H67q98jH0VPSPp546+EnkiR2g== dependencies: debug "^4.1.0" extract-zip "^1.6.6" https-proxy-agent "^3.0.0" jpeg-js "^0.3.6" - mime "^2.0.3" pngjs "^3.4.0" progress "^2.0.3" proxy-from-env "^1.0.0" - rimraf "^2.6.1" + rimraf "^3.0.2" uuid "^3.4.0" ws "^6.1.0" -playwright@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/playwright/-/playwright-0.10.0.tgz#d37f7e42e0e868dcc4ec35cb0a8dbc6248457642" - integrity sha512-f3VRME/PIO5NbcWnlCDfXwPC0DAZJ7ETkcAdE+sensLCOkfDtLh97E71ZuxNCaPYsUA6FIPi5syD8pHJW/4hQQ== +playwright@0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-0.11.0.tgz#2abec99ea278b220bcd3902d7520ec22abc2d97e" + integrity sha512-cTJZ06OhwseMC9+D6KX1NmZXyEoaJl0o6GLkDhwmou3IFTrUFVOw7KYMBpcbJz0Rhb/de5ZPFlDTffLfEy/9lg== dependencies: - playwright-core "=0.10.0" + playwright-core "=0.11.0" plist@^3.0.1: version "3.0.1" @@ -7952,6 +7946,13 @@ rimraf@^2.4.2, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2: dependencies: glob "^7.0.5" +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + rimraf@~2.2.6: version "2.2.8" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.2.8.tgz#e439be2aaee327321952730f99a8929e4fc50582" From 968b019aa5d786955a0cd136bca69d65ab01fb58 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 14 Feb 2020 10:17:10 +0100 Subject: [PATCH 032/109] text files - add tests for save from within participant --- .../textfile/common/textFileEditorModel.ts | 2 - .../test/browser/textFileEditorModel.test.ts | 59 ++++++++++++++++--- 2 files changed, 51 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index d5957f7c7d40..6186973a5b8c 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -670,9 +670,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // participant triggering this.logService.trace(`[text file model] doSave(${versionId}) - before write()`, this.resource.toString()); const lastResolvedFileStat = assertIsDefined(this.lastResolvedFileStat); - const textFileEdiorModel = this; - return this.saveSequentializer.setPending(versionId, (async () => { try { const stat = await this.textFileService.write(lastResolvedFileStat.resource, textFileEdiorModel.createSnapshot(), { diff --git a/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts b/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts index aff82ec1b835..c835deecece1 100644 --- a/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts +++ b/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts @@ -503,12 +503,11 @@ suite('Files - TextFileEditorModel', () => { }); accessor.textFileService.saveParticipant = { - participate: model => { + participate: async model => { assert.ok(model.isDirty()); model.textEditorModel!.setValue('bar'); assert.ok(model.isDirty()); eventCounter++; - return Promise.resolve(); } }; @@ -542,8 +541,8 @@ suite('Files - TextFileEditorModel', () => { const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); accessor.textFileService.saveParticipant = { - participate: (model) => { - return Promise.reject(new Error('boom')); + participate: async model => { + new Error('boom'); } }; @@ -560,10 +559,9 @@ suite('Files - TextFileEditorModel', () => { let participations: boolean[] = []; accessor.textFileService.saveParticipant = { - participate: (model) => { - return timeout(10).then(() => { - participations.push(true); - }); + participate: async model => { + await timeout(10); + participations.push(true); } }; @@ -583,4 +581,49 @@ suite('Files - TextFileEditorModel', () => { assert.equal(participations.length, 1); model.dispose(); }); + + test('Save Participant, calling save from within is unsupported but does not explode (sync save)', async function () { + const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); + + await testSaveFromSaveParticipant(model, false); + + model.dispose(); + }); + + test('Save Participant, calling save from within is unsupported but does not explode (async save)', async function () { + const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); + + await testSaveFromSaveParticipant(model, true); + + model.dispose(); + }); + + async function testSaveFromSaveParticipant(model: TextFileEditorModel, async: boolean): Promise { + let savePromise: Promise; + let breakLoop = false; + + accessor.textFileService.saveParticipant = { + participate: async model => { + if (breakLoop) { + return; + } + + breakLoop = true; + + if (async) { + await timeout(10); + } + const newSavePromise = model.save(); + + // assert that this is the same promise as the outer one + assert.equal(savePromise, newSavePromise); + } + }; + + await model.load(); + model.textEditorModel!.setValue('foo'); + + savePromise = model.save(); + await savePromise; + } }); From 245358ab239977e275a2c076e0376d86781e7d0e Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 14 Feb 2020 10:30:12 +0100 Subject: [PATCH 033/109] sort categories with uncheck items atop, https://github.com/microsoft/vscode/issues/90664 --- .../contrib/bulkEdit/browser/bulkEditPane.ts | 3 +- .../bulkEdit/browser/bulkEditPreview.ts | 11 +++++- .../contrib/bulkEdit/browser/bulkEditTree.ts | 36 ++++++++++++++++++- 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts index 2d0de4bbb3d3..0970caecdbb0 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts @@ -6,7 +6,7 @@ import 'vs/css!./bulkEdit'; import { WorkbenchAsyncDataTree, TreeResourceNavigator, IOpenEvent } from 'vs/platform/list/browser/listService'; import { WorkspaceEdit } from 'vs/editor/common/modes'; -import { BulkEditElement, BulkEditDelegate, TextEditElementRenderer, FileElementRenderer, BulkEditDataSource, BulkEditIdentityProvider, FileElement, TextEditElement, BulkEditAccessibilityProvider, BulkEditAriaProvider, CategoryElementRenderer, BulkEditNaviLabelProvider, CategoryElement } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditTree'; +import { BulkEditElement, BulkEditDelegate, TextEditElementRenderer, FileElementRenderer, BulkEditDataSource, BulkEditIdentityProvider, FileElement, TextEditElement, BulkEditAccessibilityProvider, BulkEditAriaProvider, CategoryElementRenderer, BulkEditNaviLabelProvider, CategoryElement, BulkEditSorter } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditTree'; import { FuzzyScore } from 'vs/base/common/filters'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { registerThemingParticipant, ITheme, ICssStyleCollector, IThemeService } from 'vs/platform/theme/common/themeService'; @@ -135,6 +135,7 @@ export class BulkEditPane extends ViewPane { expandOnlyOnTwistieClick: true, multipleSelectionSupport: false, keyboardNavigationLabelProvider: new BulkEditNaviLabelProvider(), + sorter: new BulkEditSorter() } ); diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts index 8d7ec503497d..06bdb85376ac 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts @@ -99,6 +99,15 @@ export class BulkFileOperation { this.newUri = edit.newUri; } } + + needsConfirmation(): boolean { + for (let [, edit] of this.originalEdits) { + if (!this.parent.checked.isChecked(edit)) { + return true; + } + } + return false; + } } export class BulkCategory { @@ -230,7 +239,7 @@ export class BulkFileOperations { } operationByResource.forEach(value => this.fileOperations.push(value)); - operationByCategory.forEach(value => value.metadata.needsConfirmation ? this.categories.unshift(value) : this.categories.push(value)); + operationByCategory.forEach(value => this.categories.push(value)); // "correct" invalid parent-check child states that is // unchecked file edits (rename, create, delete) uncheck diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts index 4a50a0b6b890..544fa1027d4d 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IAsyncDataSource, ITreeRenderer, ITreeNode } from 'vs/base/browser/ui/tree/tree'; +import { IAsyncDataSource, ITreeRenderer, ITreeNode, ITreeSorter } from 'vs/base/browser/ui/tree/tree'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels'; @@ -24,6 +24,7 @@ import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { basename } from 'vs/base/common/resources'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { WorkspaceFileEdit } from 'vs/editor/common/modes'; +import { compare } from 'vs/base/common/strings'; // --- VIEW MODEL @@ -248,6 +249,39 @@ export class BulkEditDataSource implements IAsyncDataSource { + + compare(a: BulkEditElement, b: BulkEditElement): number { + if (a instanceof CategoryElement && b instanceof CategoryElement) { + // + const aConfirm = BulkEditSorter._needsConfirmation(a.category); + const bConfirm = BulkEditSorter._needsConfirmation(b.category); + if (aConfirm === bConfirm) { + return a.category.metadata.label.localeCompare(b.category.metadata.label); + } else if (aConfirm) { + return -1; + } else { + return 1; + } + } + + if (a instanceof FileElement && b instanceof FileElement) { + return compare(a.edit.uri.toString(), b.edit.uri.toString()); + } + + if (a instanceof TextEditElement && b instanceof TextEditElement) { + return Range.compareRangesUsingStarts(a.edit.textEdit.edit.range, b.edit.textEdit.edit.range); + } + + return 0; + } + + private static _needsConfirmation(a: BulkCategory): boolean { + return a.fileOperations.some(ops => ops.needsConfirmation()); + } +} + // --- ACCESSI export class BulkEditAccessibilityProvider implements IAccessibilityProvider { From 9b31cacba04749a4bacd0e9198841a79737f2424 Mon Sep 17 00:00:00 2001 From: isidor Date: Fri, 14 Feb 2020 10:31:00 +0100 Subject: [PATCH 034/109] better error message #89658 --- src/vs/workbench/contrib/debug/browser/rawDebugSession.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts index f751bebda623..da729176ce97 100644 --- a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts @@ -601,7 +601,7 @@ export class RawDebugSession implements IDisposable { private send(command: string, args: any, token?: CancellationToken, timeout?: number): Promise { return new Promise((completeDispatch, errorDispatch) => { if (!this.debugAdapter) { - errorDispatch(new Error('no debug adapter found')); + errorDispatch(new Error(nls.localize('noDebugAdapter', "No debug adapter found. Can not send '{0}'.", command))); return; } let cancelationListener: IDisposable; From 8bac008949a6e2bff1d95a4ff08b0ff758083bb3 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 14 Feb 2020 10:59:32 +0100 Subject: [PATCH 035/109] web - allow to provide commands as workbench construction options --- src/vs/code/browser/workbench/workbench.ts | 55 +++----- .../browser/openInDesktop.web.contribution.ts | 8 +- src/vs/workbench/workbench.web.api.ts | 125 ++++++++++-------- 3 files changed, 94 insertions(+), 94 deletions(-) diff --git a/src/vs/code/browser/workbench/workbench.ts b/src/vs/code/browser/workbench/workbench.ts index d17a34fbf0c8..45f6f17ce064 100644 --- a/src/vs/code/browser/workbench/workbench.ts +++ b/src/vs/code/browser/workbench/workbench.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IWorkbenchConstructionOptions, create, URI, Event, Emitter, UriComponents, ICredentialsProvider, IURLCallbackProvider, IWorkspaceProvider, IWorkspace, IApplicationLinkProvider, IApplicationLink } from 'vs/workbench/workbench.web.api'; +import { IWorkbenchConstructionOptions, create, URI, Event, Emitter, UriComponents, ICredentialsProvider, IURLCallbackProvider, IWorkspaceProvider, IWorkspace, IApplicationLink } from 'vs/workbench/workbench.web.api'; import { generateUuid } from 'vs/base/common/uuid'; import { CancellationToken } from 'vs/base/common/cancellation'; import { streamToBuffer } from 'vs/base/common/buffer'; @@ -279,39 +279,6 @@ class WorkspaceProvider implements IWorkspaceProvider { } } -class ApplicationLinkProvider { - - private links: IApplicationLink[] | undefined = undefined; - - constructor(workspace: IWorkspace) { - this.computeLink(workspace); - } - - private computeLink(workspace: IWorkspace): void { - if (!workspace) { - return; // not for empty workspaces - } - - const workspaceUri = isWorkspaceToOpen(workspace) ? workspace.workspaceUri : isFolderToOpen(workspace) ? workspace.folderUri : undefined; - if (workspaceUri) { - this.links = [{ - uri: URI.from({ - scheme: product.quality === 'stable' ? 'vscode' : 'vscode-insiders', - authority: Schemas.vscodeRemote, - path: posix.join(posix.sep, workspaceUri.authority, workspaceUri.path), - query: workspaceUri.query, - fragment: workspaceUri.fragment, - }), - label: localize('openInDesktop', "Open in Desktop") - }]; - } - } - - get provider(): IApplicationLinkProvider { - return () => this.links; - } -} - (function () { // Find config by checking for DOM @@ -375,12 +342,30 @@ class ApplicationLinkProvider { } } + // Application links ("Open in Desktop") + let applicationLinks: IApplicationLink[] | undefined = undefined; + if (workspace) { + const workspaceUri = isWorkspaceToOpen(workspace) ? workspace.workspaceUri : isFolderToOpen(workspace) ? workspace.folderUri : undefined; + if (workspaceUri) { + applicationLinks = [{ + uri: URI.from({ + scheme: product.quality === 'stable' ? 'vscode' : 'vscode-insiders', + authority: Schemas.vscodeRemote, + path: posix.join(posix.sep, workspaceUri.authority, workspaceUri.path), + query: workspaceUri.query, + fragment: workspaceUri.fragment, + }), + label: localize('openInDesktop', "Open in Desktop") + }]; + } + } + // Finally create workbench create(document.body, { ...config, workspaceProvider: new WorkspaceProvider(workspace, payload), urlCallbackProvider: new PollingURLCallbackProvider(), credentialsProvider: new LocalStorageCredentialsProvider(), - applicationLinkProvider: new ApplicationLinkProvider(workspace).provider + applicationLinks: applicationLinks }); })(); diff --git a/src/vs/workbench/contrib/openInDesktop/browser/openInDesktop.web.contribution.ts b/src/vs/workbench/contrib/openInDesktop/browser/openInDesktop.web.contribution.ts index 0b959f3abd43..9854d0f0628f 100644 --- a/src/vs/workbench/contrib/openInDesktop/browser/openInDesktop.web.contribution.ts +++ b/src/vs/workbench/contrib/openInDesktop/browser/openInDesktop.web.contribution.ts @@ -29,13 +29,13 @@ export class OpenInDesktopIndicator extends Disposable implements IWorkbenchCont ) { super(); - const links = environmentService.options?.applicationLinkProvider?.(); + const links = environmentService.options?.applicationLinks; if (Array.isArray(links) && links?.length > 0) { this.installOpenInDesktopIndicator(links); } } - private installOpenInDesktopIndicator(links: IApplicationLink[]): void { + private installOpenInDesktopIndicator(links: readonly IApplicationLink[]): void { // Register action to trigger "Open In Desktop" const registry = Registry.as(ActionExtensions.WorkbenchActions); @@ -71,7 +71,7 @@ export class OpenInDesktopAction extends Action { } async run(): Promise { - const links = this.environmentService.options?.applicationLinkProvider?.(); + const links = this.environmentService.options?.applicationLinks; if (Array.isArray(links)) { if (links.length === 1) { return this.openApplicationLink(links[0]); @@ -83,7 +83,7 @@ export class OpenInDesktopAction extends Action { return true; } - private async runWithPicker(links: IApplicationLink[]): Promise { + private async runWithPicker(links: readonly IApplicationLink[]): Promise { // Show a picker with choices const quickPick = this.quickInputService.createQuickPick(); diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index 4cd646930bb5..140711b300ea 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -80,17 +80,29 @@ interface IApplicationLink { uri: URI; /** - * A label for the link to display. + * A label for the application link to display. */ label: string; } -interface IApplicationLinkProvider { - (): IApplicationLink[] | undefined +interface ICommand { + + /** + * An identifier for the command. Commands can be executed from extensions + * using the `vscode.commands.executeCommand` API using that command ID. + */ + id: string, + + /** + * A function that is being executed with any arguments passed over. + */ + handler: (...args: any[]) => void; } interface IWorkbenchConstructionOptions { + //#region Connection related configuration + /** * The remote authority is the IP:PORT from where the workbench is served * from. It is for example being used for the websocket connections as address. @@ -108,6 +120,36 @@ interface IWorkbenchConstructionOptions { */ readonly webviewEndpoint?: string; + /** + * A factory for web sockets. + */ + readonly webSocketFactory?: IWebSocketFactory; + + /** + * A provider for resource URIs. + */ + readonly resourceUriProvider?: IResourceUriProvider; + + /** + * Resolves an external uri before it is opened. + */ + readonly resolveExternalUri?: IExternalUriResolver; + + /** + * Support for creating tunnels. + */ + readonly tunnelFactory?: ITunnelFactory; + + /** + * Support for filtering candidate ports + */ + readonly showCandidate?: IShowCandidate; + + //#endregion + + + //#region Workbench configuration + /** * A handler for opening workspaces and providing the initial workspace. */ @@ -119,16 +161,6 @@ interface IWorkbenchConstructionOptions { */ userDataProvider?: IFileSystemProvider; - /** - * A factory for web sockets. - */ - readonly webSocketFactory?: IWebSocketFactory; - - /** - * A provider for resource URIs. - */ - readonly resourceUriProvider?: IResourceUriProvider; - /** * The credentials provider to store and retrieve secrets. */ @@ -154,21 +186,6 @@ interface IWorkbenchConstructionOptions { */ readonly resolveCommonTelemetryProperties?: ICommontTelemetryPropertiesResolver; - /** - * Resolves an external uri before it is opened. - */ - readonly resolveExternalUri?: IExternalUriResolver; - - /** - * Support for creating tunnels. - */ - readonly tunnelFactory?: ITunnelFactory; - - /** - * Support for filtering candidate ports - */ - readonly showCandidate?: IShowCandidate; - /** * Provide entries for the "Open in Desktop" feature. * @@ -179,7 +196,20 @@ interface IWorkbenchConstructionOptions { * - N elements: there will be a "Open in Desktop" affordance that opens * a picker on click to select which application to open. */ - readonly applicationLinkProvider?: IApplicationLinkProvider; + readonly applicationLinks?: readonly IApplicationLink[]; + + /** + * A set of optional commands that should be registered with the commands + * registry. + * + * Note: commands can be called from extensions if the identifier is known! + */ + readonly commands?: readonly ICommand[]; + + //#endregion + + + //#region Diagnostics /** * Current logging level. Default is `LogLevel.Info`. @@ -190,20 +220,8 @@ interface IWorkbenchConstructionOptions { * Whether to enable the smoke test driver. */ readonly driver?: boolean; -} -interface ICommandHandler { - (...args: any[]): void; -} - -interface IWorkbench { - - /** - * Register a command with the provided identifier and handler with - * the workbench. The command can be called from extensions using the - * `vscode.commands.executeCommand` API. - */ - registerCommand(id: string, command: ICommandHandler): IDisposable; + //#endregion } /** @@ -211,24 +229,22 @@ interface IWorkbench { * * @param domElement the container to create the workbench in * @param options for setting up the workbench - * - * @returns the workbench facade with additional methods to call on. */ -async function create(domElement: HTMLElement, options: IWorkbenchConstructionOptions): Promise { +async function create(domElement: HTMLElement, options: IWorkbenchConstructionOptions): Promise { // Startup workbench await main(domElement, options); - // Return facade - return { - registerCommand: (id: string, command: ICommandHandler): IDisposable => { - return CommandsRegistry.registerCommand(id, (accessor, ...args: any[]) => { + // Register commands if any + if (Array.isArray(options.commands)) { + for (const command of options.commands) { + CommandsRegistry.registerCommand(command.id, (accessor, ...args: any[]) => { // we currently only pass on the arguments but not the accessor // to the command to reduce our exposure of internal API. - command(...args); + command.handler(...args); }); } - }; + } } export { @@ -237,9 +253,6 @@ export { create, IWorkbenchConstructionOptions, - // Workbench Facade - IWorkbench, - ICommandHandler, // Basic Types URI, @@ -291,5 +304,7 @@ export { // Protocol Links IApplicationLink, - IApplicationLinkProvider + + // Commands + ICommand }; From 7bc12be7468b775918deb5faf7568b0538d08f74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Fri, 14 Feb 2020 11:10:34 +0100 Subject: [PATCH 036/109] fixes #90632 (#90675) --- .../contrib/update/browser/update.ts | 60 ++++++++++--------- .../host/browser/browserHostService.ts | 4 ++ .../workbench/services/host/browser/host.ts | 5 ++ .../electron-browser/desktopHostService.ts | 10 ++++ .../test/browser/workbenchTestServices.ts | 1 + 5 files changed, 51 insertions(+), 29 deletions(-) diff --git a/src/vs/workbench/contrib/update/browser/update.ts b/src/vs/workbench/contrib/update/browser/update.ts index 91dc0c6fcafd..786014463b64 100644 --- a/src/vs/workbench/contrib/update/browser/update.ts +++ b/src/vs/workbench/contrib/update/browser/update.ts @@ -127,39 +127,41 @@ export class ProductContribution implements IWorkbenchContribution { @IHostService hostService: IHostService, @IProductService productService: IProductService ) { - if (!hostService.hasFocus) { - return; - } + hostService.hadLastFocus().then(hadLastFocus => { + if (!hadLastFocus) { + return; + } - const lastVersion = storageService.get(ProductContribution.KEY, StorageScope.GLOBAL, ''); - const shouldShowReleaseNotes = configurationService.getValue('update.showReleaseNotes'); + const lastVersion = storageService.get(ProductContribution.KEY, StorageScope.GLOBAL, ''); + const shouldShowReleaseNotes = configurationService.getValue('update.showReleaseNotes'); - // was there an update? if so, open release notes - const releaseNotesUrl = productService.releaseNotesUrl; - if (shouldShowReleaseNotes && !environmentService.args['skip-release-notes'] && releaseNotesUrl && lastVersion && productService.version !== lastVersion) { - showReleaseNotes(instantiationService, productService.version) - .then(undefined, () => { - notificationService.prompt( - severity.Info, - nls.localize('read the release notes', "Welcome to {0} v{1}! Would you like to read the Release Notes?", productService.nameLong, productService.version), - [{ - label: nls.localize('releaseNotes', "Release Notes"), - run: () => { - const uri = URI.parse(releaseNotesUrl); - openerService.open(uri); - } - }], - { sticky: true } - ); - }); - } + // was there an update? if so, open release notes + const releaseNotesUrl = productService.releaseNotesUrl; + if (shouldShowReleaseNotes && !environmentService.args['skip-release-notes'] && releaseNotesUrl && lastVersion && productService.version !== lastVersion) { + showReleaseNotes(instantiationService, productService.version) + .then(undefined, () => { + notificationService.prompt( + severity.Info, + nls.localize('read the release notes', "Welcome to {0} v{1}! Would you like to read the Release Notes?", productService.nameLong, productService.version), + [{ + label: nls.localize('releaseNotes', "Release Notes"), + run: () => { + const uri = URI.parse(releaseNotesUrl); + openerService.open(uri); + } + }], + { sticky: true } + ); + }); + } - // should we show the new license? - if (productService.licenseUrl && lastVersion && semver.satisfies(lastVersion, '<1.0.0') && semver.satisfies(productService.version, '>=1.0.0')) { - notificationService.info(nls.localize('licenseChanged', "Our license terms have changed, please click [here]({0}) to go through them.", productService.licenseUrl)); - } + // should we show the new license? + if (productService.licenseUrl && lastVersion && semver.satisfies(lastVersion, '<1.0.0') && semver.satisfies(productService.version, '>=1.0.0')) { + notificationService.info(nls.localize('licenseChanged', "Our license terms have changed, please click [here]({0}) to go through them.", productService.licenseUrl)); + } - storageService.store(ProductContribution.KEY, productService.version, StorageScope.GLOBAL); + storageService.store(ProductContribution.KEY, productService.version, StorageScope.GLOBAL); + }); } } diff --git a/src/vs/workbench/services/host/browser/browserHostService.ts b/src/vs/workbench/services/host/browser/browserHostService.ts index 7c0b27ba4905..85de4203e540 100644 --- a/src/vs/workbench/services/host/browser/browserHostService.ts +++ b/src/vs/workbench/services/host/browser/browserHostService.ts @@ -95,6 +95,10 @@ export class BrowserHostService extends Disposable implements IHostService { return document.hasFocus(); } + async hadLastFocus(): Promise { + return true; + } + async focus(): Promise { window.focus(); } diff --git a/src/vs/workbench/services/host/browser/host.ts b/src/vs/workbench/services/host/browser/host.ts index f97c8048de29..ad10de5ba655 100644 --- a/src/vs/workbench/services/host/browser/host.ts +++ b/src/vs/workbench/services/host/browser/host.ts @@ -25,6 +25,11 @@ export interface IHostService { */ readonly hasFocus: boolean; + /** + * Find out if the window had the last focus. + */ + hadLastFocus(): Promise; + /** * Attempt to bring the window to the foreground and focus it. */ diff --git a/src/vs/workbench/services/host/electron-browser/desktopHostService.ts b/src/vs/workbench/services/host/electron-browser/desktopHostService.ts index 6fd4fa2404ea..b009d228026a 100644 --- a/src/vs/workbench/services/host/electron-browser/desktopHostService.ts +++ b/src/vs/workbench/services/host/electron-browser/desktopHostService.ts @@ -36,6 +36,16 @@ export class DesktopHostService extends Disposable implements IHostService { return document.hasFocus(); } + async hadLastFocus(): Promise { + const activeWindowId = await this.electronService.getActiveWindowId(); + + if (typeof activeWindowId === 'undefined') { + return false; + } + + return activeWindowId === this.electronEnvironmentService.windowId; + } + openWindow(options?: IOpenEmptyWindowOptions): Promise; openWindow(toOpen: IWindowOpenable[], options?: IOpenWindowOptions): Promise; openWindow(arg1?: IOpenEmptyWindowOptions | IWindowOpenable[], arg2?: IOpenWindowOptions): Promise { diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 04ae11414128..67ef5397a994 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -865,6 +865,7 @@ export class TestHostService implements IHostService { _serviceBrand: undefined; readonly hasFocus: boolean = true; + async hadLastFocus(): Promise { return true; } readonly onDidChangeFocus: Event = Event.None; async restart(): Promise { } From 30d22f5f33b374ff3b7f21ca2bc829386c34998a Mon Sep 17 00:00:00 2001 From: isidor Date: Fri, 14 Feb 2020 11:22:28 +0100 Subject: [PATCH 037/109] fixes #90660 --- src/vs/workbench/contrib/debug/browser/startView.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/startView.ts b/src/vs/workbench/contrib/debug/browser/startView.ts index 923462eee949..94de2fe10941 100644 --- a/src/vs/workbench/contrib/debug/browser/startView.ts +++ b/src/vs/workbench/contrib/debug/browser/startView.ts @@ -20,13 +20,14 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { equals } from 'vs/base/common/arrays'; -import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { BaseDebugViewPane } from 'vs/workbench/contrib/debug/browser/baseDebugView'; const $ = dom.$; interface DebugStartMetrics { @@ -51,7 +52,7 @@ function createClickElement(textContent: string, action: () => any): HTMLSpanEle return clickElement; } -export class StartView extends ViewPane { +export class StartView extends BaseDebugViewPane { static ID = 'workbench.debug.startView'; static LABEL = localize('start', "Start"); From adfa13a302a8f54cf6371c16fd2b7d108066b0c5 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 14 Feb 2020 11:31:14 +0100 Subject: [PATCH 038/109] Render narrow characters for larger font scales --- .../browser/viewParts/minimap/minimap.ts | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index 3e0a325df4c2..0a67ece80520 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -138,7 +138,15 @@ class MinimapOptions { const desiredRatio = (modelLineCount + extraLinesBeyondLastLine) / (pixelRatio * layoutInfo.height); minimapLineHeight = Math.max(1, Math.floor(1 / desiredRatio)); // fontScale = Math.round(minimapOpts.scale * pixelRatio); - fontScale = Math.max(1, Math.floor(minimapLineHeight / baseCharHeight / 2)); + + const configuredFontScale = Math.round(minimapOpts.scale * pixelRatio); + fontScale = Math.min(4, Math.max(1, Math.floor(minimapLineHeight / baseCharHeight))); + + const fontScaleRatio = fontScale / configuredFontScale; + if (fontScaleRatio > 1) { + this.canvasInnerWidth = Math.floor(this.canvasInnerWidth * fontScaleRatio); + } + const actualMinimapHeight = (modelLineCount + extraLinesBeyondLastLine) * minimapLineHeight; this.canvasInnerHeight = Math.ceil(actualMinimapHeight); } @@ -170,7 +178,6 @@ class MinimapOptions { && this.pixelRatio === other.pixelRatio && this.typicalHalfwidthCharacterWidth === other.typicalHalfwidthCharacterWidth && this.lineHeight === other.lineHeight - && this.fontScale === other.fontScale && this.minimapLeft === other.minimapLeft && this.minimapWidth === other.minimapWidth && this.minimapHeight === other.minimapHeight @@ -179,6 +186,9 @@ class MinimapOptions { && this.canvasOuterWidth === other.canvasOuterWidth && this.canvasOuterHeight === other.canvasOuterHeight && this.backgroundColor.equals(other.backgroundColor) + && this.fontScale === other.fontScale + && this.minimapLineHeight === other.minimapLineHeight + && this.minimapCharWidth === other.minimapCharWidth ); } } @@ -1529,7 +1539,6 @@ class InnerMinimap extends Disposable { const useLighterFont = tokensColorTracker.backgroundIsLight(); const renderMinimap = this._model.options.renderMinimap; const charRenderer = this._model.options.charRenderer(); - const isSampling = this._model.options.isSampling; const fontScale = this._model.options.fontScale; const minimapCharWidth = this._model.options.minimapCharWidth; @@ -1550,7 +1559,7 @@ class InnerMinimap extends Disposable { tabSize, lineInfo[lineIndex]!, fontScale, - isSampling + minimapLineHeight ); } renderedLines[lineIndex] = new MinimapLine(dy); @@ -1677,7 +1686,7 @@ class InnerMinimap extends Disposable { tabSize: number, lineData: ViewLineData, fontScale: number, - isSampling: boolean + minimapLineHeight: number ): void { const content = lineData.content; const tokens = lineData.tokens; @@ -1715,7 +1724,7 @@ class InnerMinimap extends Disposable { if (renderMinimap === RenderMinimap.Blocks) { minimapCharRenderer.blockRenderChar(target, dx, dy, tokenColor, backgroundColor, useLighterFont); } else { // RenderMinimap.Text - minimapCharRenderer.renderChar(target, dx, dy, charCode, tokenColor, backgroundColor, fontScale, useLighterFont, isSampling); + minimapCharRenderer.renderChar(target, dx, dy, charCode, tokenColor, backgroundColor, fontScale, useLighterFont, minimapLineHeight === 1); } dx += charWidth; From c379e3226e5458348ebb115c03931bb807b76d7b Mon Sep 17 00:00:00 2001 From: isidor Date: Fri, 14 Feb 2020 11:31:35 +0100 Subject: [PATCH 039/109] debug: remove BaseDebugViewPane --- src/vs/workbench/contrib/debug/browser/baseDebugView.ts | 9 --------- .../workbench/contrib/debug/browser/breakpointsView.ts | 6 +++--- src/vs/workbench/contrib/debug/browser/callStackView.ts | 8 ++++---- .../workbench/contrib/debug/browser/loadedScriptsView.ts | 7 ++++--- src/vs/workbench/contrib/debug/browser/startView.ts | 6 +++--- src/vs/workbench/contrib/debug/browser/variablesView.ts | 7 ++++--- .../contrib/debug/browser/watchExpressionsView.ts | 7 ++++--- 7 files changed, 22 insertions(+), 28 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts index 3b75c468a51c..5d4be69a867c 100644 --- a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts +++ b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts @@ -19,7 +19,6 @@ import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; import { ReplEvaluationResult } from 'vs/workbench/contrib/debug/common/replModel'; import { once } from 'vs/base/common/functional'; -import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; export const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024; export const twistiePixels = 20; @@ -233,11 +232,3 @@ export abstract class AbstractExpressionsRenderer implements ITreeRenderer; private needsRefresh = false; @@ -82,6 +81,7 @@ export class BreakpointsView extends BaseDebugViewPane { public renderBody(container: HTMLElement): void { super.renderBody(container); + dom.addClass(this.element, 'debug-pane'); dom.addClass(container, 'debug-breakpoints'); const delegate = new BreakpointsDelegate(this.debugService); diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index 805bac9c9166..5ae2c952efe1 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -13,12 +13,12 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { MenuId, IMenu, IMenuService } from 'vs/platform/actions/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { renderViewTree, BaseDebugViewPane } from 'vs/workbench/contrib/debug/browser/baseDebugView'; +import { renderViewTree } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import { IAction, Action } from 'vs/base/common/actions'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { ILabelService } from 'vs/platform/label/common/label'; import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; @@ -74,7 +74,7 @@ export function getContextForContributedActions(element: CallStackItem | null): return ''; } -export class CallStackView extends BaseDebugViewPane { +export class CallStackView extends ViewPane { private pauseMessage!: HTMLSpanElement; private pauseMessageLabel!: HTMLSpanElement; private onCallStackChangeScheduler: RunOnceScheduler; @@ -158,7 +158,7 @@ export class CallStackView extends BaseDebugViewPane { renderBody(container: HTMLElement): void { super.renderBody(container); - + dom.addClass(this.element, 'debug-pane'); dom.addClass(container, 'debug-call-stack'); const treeContainer = renderViewTree(container); diff --git a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts index bf56471c4a41..ceb03abfcc97 100644 --- a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts +++ b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts @@ -7,12 +7,12 @@ import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { normalize, isAbsolute, posix } from 'vs/base/common/path'; -import { IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { renderViewTree, BaseDebugViewPane } from 'vs/workbench/contrib/debug/browser/baseDebugView'; +import { renderViewTree } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import { IDebugSession, IDebugService, CONTEXT_LOADED_SCRIPTS_ITEM_TYPE } from 'vs/workbench/contrib/debug/common/debug'; import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; @@ -402,7 +402,7 @@ function asTreeElement(item: BaseTreeItem, viewState?: IViewState): ITreeElement }; } -export class LoadedScriptsView extends BaseDebugViewPane { +export class LoadedScriptsView extends ViewPane { private treeContainer!: HTMLElement; private loadedScriptsItemType: IContextKey; @@ -435,6 +435,7 @@ export class LoadedScriptsView extends BaseDebugViewPane { renderBody(container: HTMLElement): void { super.renderBody(container); + dom.addClass(this.element, 'debug-pane'); dom.addClass(container, 'debug-loaded-scripts'); dom.addClass(container, 'show-file-icons'); diff --git a/src/vs/workbench/contrib/debug/browser/startView.ts b/src/vs/workbench/contrib/debug/browser/startView.ts index 94de2fe10941..f561b8cc42b1 100644 --- a/src/vs/workbench/contrib/debug/browser/startView.ts +++ b/src/vs/workbench/contrib/debug/browser/startView.ts @@ -20,14 +20,13 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { equals } from 'vs/base/common/arrays'; -import { IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { BaseDebugViewPane } from 'vs/workbench/contrib/debug/browser/baseDebugView'; const $ = dom.$; interface DebugStartMetrics { @@ -52,7 +51,7 @@ function createClickElement(textContent: string, action: () => any): HTMLSpanEle return clickElement; } -export class StartView extends BaseDebugViewPane { +export class StartView extends ViewPane { static ID = 'workbench.debug.startView'; static LABEL = localize('start', "Start"); @@ -171,6 +170,7 @@ export class StartView extends BaseDebugViewPane { })); attachButtonStyler(this.debugButton, this.themeService); + dom.addClass(this.element, 'debug-pane'); dom.addClass(container, 'debug-start-view'); this.secondMessageContainer = $('.section'); diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index 8e9dd8dc2b05..4dd9c4bfac09 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -12,12 +12,12 @@ import { IDebugService, IExpression, IScope, CONTEXT_VARIABLES_FOCUSED, IViewMod import { Variable, Scope } from 'vs/workbench/contrib/debug/common/debugModel'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { renderViewTree, renderVariable, IInputBoxOptions, AbstractExpressionsRenderer, IExpressionTemplateData, BaseDebugViewPane } from 'vs/workbench/contrib/debug/browser/baseDebugView'; +import { renderViewTree, renderVariable, IInputBoxOptions, AbstractExpressionsRenderer, IExpressionTemplateData } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import { IAction, Action } from 'vs/base/common/actions'; import { CopyValueAction } from 'vs/workbench/contrib/debug/browser/debugActions'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ITreeRenderer, ITreeNode, ITreeMouseEvent, ITreeContextMenuEvent, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; @@ -39,7 +39,7 @@ let forgetScopes = true; export const variableSetEmitter = new Emitter(); -export class VariablesView extends BaseDebugViewPane { +export class VariablesView extends ViewPane { private onFocusStackFrameScheduler: RunOnceScheduler; private needsRefresh = false; @@ -90,6 +90,7 @@ export class VariablesView extends BaseDebugViewPane { renderBody(container: HTMLElement): void { super.renderBody(container); + dom.addClass(this.element, 'debug-pane'); dom.addClass(container, 'debug-variables'); const treeContainer = renderViewTree(container); diff --git a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index df6770a121f2..99b9cc6d32e6 100644 --- a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts +++ b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts @@ -16,9 +16,9 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IAction, Action } from 'vs/base/common/actions'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; -import { renderExpressionValue, renderViewTree, IInputBoxOptions, AbstractExpressionsRenderer, IExpressionTemplateData, BaseDebugViewPane } from 'vs/workbench/contrib/debug/browser/baseDebugView'; +import { renderExpressionValue, renderViewTree, IInputBoxOptions, AbstractExpressionsRenderer, IExpressionTemplateData } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; @@ -38,7 +38,7 @@ const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024; let ignoreVariableSetEmitter = false; let useCachedEvaluation = false; -export class WatchExpressionsView extends BaseDebugViewPane { +export class WatchExpressionsView extends ViewPane { private onWatchExpressionsUpdatedScheduler: RunOnceScheduler; private needsRefresh = false; @@ -67,6 +67,7 @@ export class WatchExpressionsView extends BaseDebugViewPane { renderBody(container: HTMLElement): void { super.renderBody(container); + dom.addClass(this.element, 'debug-pane'); dom.addClass(container, 'debug-watch'); const treeContainer = renderViewTree(container); From 6158372c890059fd03b0224e19b1af0e385e5725 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 14 Feb 2020 12:05:01 +0100 Subject: [PATCH 040/109] more verbose integration test failures --- .../src/singlefolder-tests/workspace.test.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts index 25b34c5827b7..5810129fdc16 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts @@ -245,6 +245,10 @@ suite('workspace-namespace', () => { sub.dispose(); }); + function assertEqualPath(a: string, b: string): void { + assert.ok(pathEquals(a, b), `${a} <-> ${b}`); + } + test('events: onDidOpenTextDocument, onDidChangeTextDocument, onDidSaveTextDocument', async () => { const file = await createRandomFile(); let disposables: vscode.Disposable[] = []; @@ -252,19 +256,19 @@ suite('workspace-namespace', () => { let pendingAsserts: Function[] = []; let onDidOpenTextDocument = false; disposables.push(vscode.workspace.onDidOpenTextDocument(e => { - pendingAsserts.push(() => assert.ok(pathEquals(e.uri.fsPath, file.fsPath))); + pendingAsserts.push(() => assertEqualPath(e.uri.fsPath, file.fsPath)); onDidOpenTextDocument = true; })); let onDidChangeTextDocument = false; disposables.push(vscode.workspace.onDidChangeTextDocument(e => { - pendingAsserts.push(() => assert.ok(pathEquals(e.document.uri.fsPath, file.fsPath))); + pendingAsserts.push(() => assertEqualPath(e.document.uri.fsPath, file.fsPath)); onDidChangeTextDocument = true; })); let onDidSaveTextDocument = false; disposables.push(vscode.workspace.onDidSaveTextDocument(e => { - pendingAsserts.push(() => assert.ok(pathEquals(e.uri.fsPath, file.fsPath))); + pendingAsserts.push(() => assertEqualPath(e.uri.fsPath, file.fsPath)); onDidSaveTextDocument = true; })); @@ -291,7 +295,7 @@ suite('workspace-namespace', () => { let onDidSaveTextDocument = false; disposables.push(vscode.workspace.onDidSaveTextDocument(e => { - pendingAsserts.push(() => assert.ok(pathEquals(e.uri.fsPath, file.fsPath))); + pendingAsserts.push(() => assertEqualPath(e.uri.fsPath, file.fsPath)); onDidSaveTextDocument = true; })); From bcdf991bd47c5bf11d8a86eec4ae0b708c2708ed Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 14 Feb 2020 12:05:57 +0100 Subject: [PATCH 041/109] test: saveAll before asserting a certain save event --- .../vscode-api-tests/src/singlefolder-tests/workspace.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts index 5810129fdc16..3e56f9271680 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts @@ -253,6 +253,8 @@ suite('workspace-namespace', () => { const file = await createRandomFile(); let disposables: vscode.Disposable[] = []; + await vscode.workspace.saveAll(); + let pendingAsserts: Function[] = []; let onDidOpenTextDocument = false; disposables.push(vscode.workspace.onDidOpenTextDocument(e => { From b3a55da2dcbe5111026518cc4f10eca0f2a0ca1e Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 14 Feb 2020 12:18:53 +0100 Subject: [PATCH 042/109] Center lines vertically --- src/vs/editor/browser/viewParts/minimap/minimap.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index 0a67ece80520..06a7f83f5fef 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -1542,6 +1542,10 @@ class InnerMinimap extends Disposable { const fontScale = this._model.options.fontScale; const minimapCharWidth = this._model.options.minimapCharWidth; + const baseCharHeight = (renderMinimap === RenderMinimap.Text ? Constants.BASE_CHAR_HEIGHT : Constants.BASE_CHAR_HEIGHT + 1); + const renderMinimapLineHeight = baseCharHeight * fontScale; + const innerLinePadding = (minimapLineHeight > renderMinimapLineHeight ? Math.floor((minimapLineHeight - renderMinimapLineHeight) / 2) : 0); + // Render the rest of lines let dy = 0; const renderedLines: MinimapLine[] = []; @@ -1556,6 +1560,7 @@ class InnerMinimap extends Disposable { tokensColorTracker, charRenderer, dy, + innerLinePadding, tabSize, lineInfo[lineIndex]!, fontScale, @@ -1683,6 +1688,7 @@ class InnerMinimap extends Disposable { colorTracker: MinimapTokensColorTracker, minimapCharRenderer: MinimapCharRenderer, dy: number, + innerLinePadding: number, tabSize: number, lineData: ViewLineData, fontScale: number, @@ -1722,9 +1728,9 @@ class InnerMinimap extends Disposable { for (let i = 0; i < count; i++) { if (renderMinimap === RenderMinimap.Blocks) { - minimapCharRenderer.blockRenderChar(target, dx, dy, tokenColor, backgroundColor, useLighterFont); + minimapCharRenderer.blockRenderChar(target, dx, dy + innerLinePadding, tokenColor, backgroundColor, useLighterFont); } else { // RenderMinimap.Text - minimapCharRenderer.renderChar(target, dx, dy, charCode, tokenColor, backgroundColor, fontScale, useLighterFont, minimapLineHeight === 1); + minimapCharRenderer.renderChar(target, dx, dy + innerLinePadding, charCode, tokenColor, backgroundColor, fontScale, useLighterFont, minimapLineHeight === 1); } dx += charWidth; From aa9f9695c42ee71e155548eb29ddc10b425de6a1 Mon Sep 17 00:00:00 2001 From: isidor Date: Fri, 14 Feb 2020 12:22:43 +0100 Subject: [PATCH 043/109] debug: fix some css when view moved to napel --- .../contrib/debug/browser/media/debug.contribution.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css index 12a93e85e7d9..2fb7b93bd76c 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css +++ b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css @@ -75,7 +75,7 @@ /* Expressions */ -.monaco-workbench .debug-viewlet .monaco-list-row .expression, +.monaco-workbench .debug-pane .monaco-list-row .expression, .monaco-workbench .debug-hover-widget .monaco-list-row .expression { font-size: 13px; overflow: hidden; @@ -84,7 +84,7 @@ white-space: pre; } -.monaco-workbench.mac .debug-viewlet .monaco-list-row .expression, +.monaco-workbench.mac .debug-pane .monaco-list-row .expression, .monaco-workbench.mac .debug-hover-widget .monaco-list-row .expression { font-size: 11px; } From e588a9cd1ad1d2794a592e5d14cb3ab5bd2818ea Mon Sep 17 00:00:00 2001 From: Andre Weinand Date: Fri, 14 Feb 2020 12:47:52 +0100 Subject: [PATCH 044/109] Introduce evaluatable expression API; fixes #89084 --- src/vs/editor/common/modes.ts | 33 ++++++++++ src/vs/monaco.d.ts | 25 ++++++++ src/vs/vscode.proposed.d.ts | 60 ++++++++++++++++- .../api/browser/mainThreadLanguageFeatures.ts | 10 +++ .../workbench/api/common/extHost.api.impl.ts | 4 ++ .../workbench/api/common/extHost.protocol.ts | 2 + .../api/common/extHostLanguageFeatures.ts | 35 +++++++++- .../api/common/extHostTypeConverters.ts | 14 ++++ src/vs/workbench/api/common/extHostTypes.ts | 11 ++++ .../contrib/debug/browser/debugHover.ts | 64 +++++++++++++++---- 10 files changed, 243 insertions(+), 15 deletions(-) diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 7d925b4bc2c0..0340900a5c44 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -265,6 +265,34 @@ export interface HoverProvider { provideHover(model: model.ITextModel, position: Position, token: CancellationToken): ProviderResult; } +/** + * An evaluatable expression represents additional information for an expression in a document. Evaluatable expression are + * evaluated by a debugger or runtime and their result is rendered in a tooltip-like widget. + */ +export interface EvaluatableExpression { + /** + * The range to which this expression applies. + */ + range: IRange; + /* + * This expression overrides the expression extracted from the range. + */ + expression?: string; +} + +/** + * The hover provider interface defines the contract between extensions and + * the [hover](https://code.visualstudio.com/docs/editor/intellisense)-feature. + */ +export interface EvaluatableExpressionProvider { + /** + * Provide a hover for the given position and document. Multiple hovers at the same + * position will be merged by the editor. A hover can have a range which defaults + * to the word range at the position when omitted. + */ + provideEvaluatableExpression(model: model.ITextModel, position: Position, token: CancellationToken): ProviderResult; +} + export const enum CompletionItemKind { Method, Function, @@ -1595,6 +1623,11 @@ export const SignatureHelpProviderRegistry = new LanguageFeatureRegistry(); +/** + * @internal + */ +export const EvaluatableExpressionProviderRegistry = new LanguageFeatureRegistry(); + /** * @internal */ diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index ff031806fb3f..bee004f486d1 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -5238,6 +5238,31 @@ declare namespace monaco.languages { provideHover(model: editor.ITextModel, position: Position, token: CancellationToken): ProviderResult; } + /** + * An evaluatable expression represents additional information for an expression in a document. Evaluatable expression are + * evaluated by a debugger or runtime and their result is rendered in a tooltip-like widget. + */ + export interface EvaluatableExpression { + /** + * The range to which this expression applies. + */ + range: IRange; + expression?: string; + } + + /** + * The hover provider interface defines the contract between extensions and + * the [hover](https://code.visualstudio.com/docs/editor/intellisense)-feature. + */ + export interface EvaluatableExpressionProvider { + /** + * Provide a hover for the given position and document. Multiple hovers at the same + * position will be merged by the editor. A hover can have a range which defaults + * to the word range at the position when omitted. + */ + provideEvaluatableExpression(model: editor.ITextModel, position: Position, token: CancellationToken): ProviderResult; + } + export enum CompletionItemKind { Method = 0, Function = 1, diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index a782d93c1a65..deb1bae00507 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -876,7 +876,65 @@ declare module 'vscode' { //#endregion - //#region Debug: + //#region locate evaluatable expressions for debug hover: https://github.com/microsoft/vscode/issues/89084 + + /** + * An EvaluatableExpression represents an expression in a document that can be evaluated by an active debugger or runtime. + * The result of this evaluation is shown in a tooltip-like widget. + * If only a range is specified, the expression will be extracted from the underlying document. + * An optional expression can be used to override the extracted expression. + * In this case the range is still used to highlight the range in the document. + */ + export class EvaluatableExpression { + /* + * The range is used to extract the evaluatable expression from the underlying document and to highlight it. + */ + readonly range: Range; + /* + * If specified the expression overrides the extracted expression. + */ + readonly expression?: string; + + /** + * Creates a new evaluatable expression object. + * + * @param range The range in the underlying document from which the evaluatable expression is extracted. + * @param expression If specified overrides the extracted expression. + */ + constructor(range: Range, expression?: string); + } + + /** + * The evaluatable expression provider interface defines the contract between extensions and + * the debug hover. + */ + export interface EvaluatableExpressionProvider { + + /** + * Provide an evaluatable expression for the given document and position. + * The expression can be implicitly specified by the range in the underlying document or by explicitly returning an expression. + * + * @param document The document in which the command was invoked. + * @param position The position where the command was invoked. + * @param token A cancellation token. + * @return An EvaluatableExpression or a thenable that resolves to such. The lack of a result can be + * signaled by returning `undefined` or `null`. + */ + provideEvaluatableExpression(document: TextDocument, position: Position, token: CancellationToken): ProviderResult; + } + + export namespace languages { + /** + * Register a provider that locates evaluatable expressions in text documents. + * + * If multiple providers are registered for a language an arbitrary provider will be used. + * + * @param selector A selector that defines the documents this provider is applicable to. + * @param provider An evaluatable expression provider. + * @return A [disposable](#Disposable) that unregisters this provider when being disposed. + */ + export function registerEvaluatableExpressionProvider(selector: DocumentSelector, provider: EvaluatableExpressionProvider): Disposable; + } // deprecated diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 678a44ca3f3e..de204a88dbda 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -213,6 +213,16 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha })); } + // --- debug hover + + $registerEvaluatableExpressionProvider(handle: number, selector: IDocumentFilterDto[]): void { + this._registrations.set(handle, modes.EvaluatableExpressionProviderRegistry.register(selector, { + provideEvaluatableExpression: (model: ITextModel, position: EditorPosition, token: CancellationToken): Promise => { + return this._proxy.$provideEvaluatableExpression(handle, model.uri, position, token); + } + })); + } + // --- occurrences $registerDocumentHighlightProvider(handle: number, selector: IDocumentFilterDto[]): void { diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 3952717d2206..61353fa26834 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -345,6 +345,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I registerHoverProvider(selector: vscode.DocumentSelector, provider: vscode.HoverProvider): vscode.Disposable { return extHostLanguageFeatures.registerHoverProvider(extension, checkSelector(selector), provider, extension.identifier); }, + registerEvaluatableExpressionProvider(selector: vscode.DocumentSelector, provider: vscode.EvaluatableExpressionProvider): vscode.Disposable { + return extHostLanguageFeatures.registerEvaluatableExpressionProvider(extension, checkSelector(selector), provider, extension.identifier); + }, registerDocumentHighlightProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentHighlightProvider): vscode.Disposable { return extHostLanguageFeatures.registerDocumentHighlightProvider(extension, checkSelector(selector), provider); }, @@ -926,6 +929,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I DocumentLink: extHostTypes.DocumentLink, DocumentSymbol: extHostTypes.DocumentSymbol, EndOfLine: extHostTypes.EndOfLine, + EvaluatableExpression: extHostTypes.EvaluatableExpression, EventEmitter: Emitter, ExtensionKind: extHostTypes.ExtensionKind, CustomExecution: extHostTypes.CustomExecution, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index c181215c41d3..73af521adefb 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -353,6 +353,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $registerImplementationSupport(handle: number, selector: IDocumentFilterDto[]): void; $registerTypeDefinitionSupport(handle: number, selector: IDocumentFilterDto[]): void; $registerHoverProvider(handle: number, selector: IDocumentFilterDto[]): void; + $registerEvaluatableExpressionProvider(handle: number, selector: IDocumentFilterDto[]): void; $registerDocumentHighlightProvider(handle: number, selector: IDocumentFilterDto[]): void; $registerReferenceSupport(handle: number, selector: IDocumentFilterDto[]): void; $registerQuickFixSupport(handle: number, selector: IDocumentFilterDto[], metadata: ICodeActionProviderMetadataDto): void; @@ -1208,6 +1209,7 @@ export interface ExtHostLanguageFeaturesShape { $provideImplementation(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; $provideTypeDefinition(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; $provideHover(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; + $provideEvaluatableExpression(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; $provideDocumentHighlights(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; $provideReferences(handle: number, resource: UriComponents, position: IPosition, context: modes.ReferenceContext, token: CancellationToken): Promise; $provideCodeActions(handle: number, resource: UriComponents, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext, token: CancellationToken): Promise; diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index b90e3ddeb24f..65d12628b105 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -276,6 +276,27 @@ class HoverAdapter { } } +class EvaluatableExpressionAdapter { + + constructor( + private readonly _documents: ExtHostDocuments, + private readonly _provider: vscode.EvaluatableExpressionProvider, + ) { } + + public provideEvaluatableExpression(resource: URI, position: IPosition, token: CancellationToken): Promise { + + const doc = this._documents.getDocument(resource); + const pos = typeConvert.Position.to(position); + + return asPromise(() => this._provider.provideEvaluatableExpression(doc, pos, token)).then(value => { + if (value) { + return typeConvert.EvaluatableExpression.from(value); + } + return undefined; + }); + } +} + class DocumentHighlightAdapter { constructor( @@ -1329,7 +1350,7 @@ type Adapter = DocumentSymbolAdapter | CodeLensAdapter | DefinitionAdapter | Hov | RangeFormattingAdapter | OnTypeFormattingAdapter | NavigateTypeAdapter | RenameAdapter | SuggestAdapter | SignatureHelpAdapter | LinkProviderAdapter | ImplementationAdapter | TypeDefinitionAdapter | ColorProviderAdapter | FoldingProviderAdapter | DeclarationAdapter - | SelectionRangeAdapter | CallHierarchyAdapter | DocumentSemanticTokensAdapter | DocumentRangeSemanticTokensAdapter; + | SelectionRangeAdapter | CallHierarchyAdapter | DocumentSemanticTokensAdapter | DocumentRangeSemanticTokensAdapter | EvaluatableExpressionAdapter; class AdapterData { constructor( @@ -1549,6 +1570,18 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF return this._withAdapter(handle, HoverAdapter, adapter => adapter.provideHover(URI.revive(resource), position, token), undefined); } + // --- debug hover + + registerEvaluatableExpressionProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.EvaluatableExpressionProvider, extensionId?: ExtensionIdentifier): vscode.Disposable { + const handle = this._addNewAdapter(new EvaluatableExpressionAdapter(this._documents, provider), extension); + this._proxy.$registerEvaluatableExpressionProvider(handle, this._transformDocumentSelector(selector)); + return this._createDisposable(handle); + } + + $provideEvaluatableExpression(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise { + return this._withAdapter(handle, EvaluatableExpressionAdapter, adapter => adapter.provideEvaluatableExpression(URI.revive(resource), position, token), undefined); + } + // --- occurrences registerDocumentHighlightProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentHighlightProvider): vscode.Disposable { diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 72c238ee6ba7..c998bd2c5f88 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -737,6 +737,20 @@ export namespace Hover { return new types.Hover(info.contents.map(MarkdownString.to), Range.to(info.range)); } } + +export namespace EvaluatableExpression { + export function from(expression: vscode.EvaluatableExpression): modes.EvaluatableExpression { + return { + range: Range.from(expression.range), + expression: expression.expression + }; + } + + export function to(info: modes.EvaluatableExpression): types.EvaluatableExpression { + return new types.EvaluatableExpression(Range.to(info.range), info.expression); + } +} + export namespace DocumentHighlight { export function from(documentHighlight: vscode.DocumentHighlight): modes.DocumentHighlight { return { diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index f9f9de267584..f6871d7e2bda 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -2283,6 +2283,17 @@ export class DebugAdapterInlineImplementation implements vscode.DebugAdapterInli } } +@es5ClassCompat +export class EvaluatableExpression implements vscode.EvaluatableExpression { + readonly range: vscode.Range; + readonly expression?: string; + + constructor(range: vscode.Range, expression?: string) { + this.range = range; + this.expression = expression; + } +} + export enum LogLevel { Trace = 1, Debug = 2, diff --git a/src/vs/workbench/contrib/debug/browser/debugHover.ts b/src/vs/workbench/contrib/debug/browser/debugHover.ts index 0571de088e8a..bb510b66fd70 100644 --- a/src/vs/workbench/contrib/debug/browser/debugHover.ts +++ b/src/vs/workbench/contrib/debug/browser/debugHover.ts @@ -11,7 +11,7 @@ import * as dom from 'vs/base/browser/dom'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions'; import { Position } from 'vs/editor/common/core/position'; -import { Range } from 'vs/editor/common/core/range'; +import { Range, IRange } from 'vs/editor/common/core/range'; import { IContentWidget, ICodeEditor, IContentWidgetPosition, ContentWidgetPositionPreference } from 'vs/editor/browser/editorBrowser'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IDebugService, IExpression, IExpressionContainer, IStackFrame } from 'vs/workbench/contrib/debug/common/debug'; @@ -30,6 +30,8 @@ import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; import { coalesce } from 'vs/base/common/arrays'; import { IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; import { VariablesRenderer } from 'vs/workbench/contrib/debug/browser/variablesView'; +import { EvaluatableExpressionProviderRegistry } from 'vs/editor/common/modes'; +import { CancellationToken } from 'vs/base/common/cancellation'; const $ = dom.$; const MAX_TREE_HEIGHT = 324; @@ -174,18 +176,52 @@ export class DebugHoverWidget implements IContentWidget { } async showAt(range: Range, focus: boolean): Promise { - const pos = range.getStartPosition(); const session = this.debugService.getViewModel().focusedSession; - if (!this.editor.hasModel()) { + + if (!session || !this.editor.hasModel()) { return Promise.resolve(this.hide()); } - const lineContent = this.editor.getModel().getLineContent(pos.lineNumber); - const { start, end } = getExactExpressionStartAndEnd(lineContent, range.startColumn, range.endColumn); - // use regex to extract the sub-expression #9821 - const matchingExpression = lineContent.substring(start - 1, end); - if (!matchingExpression || !session) { + const model = this.editor.getModel(); + const pos = range.getStartPosition(); + + let rng: IRange | undefined = undefined; + let matchingExpression: string | undefined; + + if (EvaluatableExpressionProviderRegistry.has(model)) { + const supports = EvaluatableExpressionProviderRegistry.ordered(model); + + const promises = supports.map(support => { + return Promise.resolve(support.provideEvaluatableExpression(model, pos, CancellationToken.None)).then(expression => { + return expression; + }, err => { + //onUnexpectedExternalError(err); + return undefined; + }); + }); + + const results = await Promise.all(promises).then(coalesce); + if (results.length > 0) { + matchingExpression = results[0].expression; + rng = results[0].range; + + if (!matchingExpression) { + const lineContent = model.getLineContent(pos.lineNumber); + matchingExpression = lineContent.substring(rng.startColumn - 1, rng.endColumn); + } + } + + } else { // old one-size-fits-all strategy + const lineContent = model.getLineContent(pos.lineNumber); + const { start, end } = getExactExpressionStartAndEnd(lineContent, range.startColumn, range.endColumn); + + // use regex to extract the sub-expression #9821 + matchingExpression = lineContent.substring(start - 1, end); + rng = new Range(pos.lineNumber, start, pos.lineNumber, start + matchingExpression.length); + } + + if (!matchingExpression) { return Promise.resolve(this.hide()); } @@ -202,13 +238,15 @@ export class DebugHoverWidget implements IContentWidget { if (!expression || (expression instanceof Expression && !expression.available)) { this.hide(); - return undefined; + return; } - this.highlightDecorations = this.editor.deltaDecorations(this.highlightDecorations, [{ - range: new Range(pos.lineNumber, start, pos.lineNumber, start + matchingExpression.length), - options: DebugHoverWidget._HOVER_HIGHLIGHT_DECORATION_OPTIONS - }]); + if (rng) { + this.highlightDecorations = this.editor.deltaDecorations(this.highlightDecorations, [{ + range: rng, + options: DebugHoverWidget._HOVER_HIGHLIGHT_DECORATION_OPTIONS + }]); + } return this.doShow(pos, expression, focus); } From 802fdbac9f2aa6670fb426a59081737f4b22f91e Mon Sep 17 00:00:00 2001 From: Andre Weinand Date: Fri, 14 Feb 2020 12:53:37 +0100 Subject: [PATCH 045/109] don't work too much unneccesarily... --- .../contrib/debug/browser/debugEditorContribution.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts index c22007db2f24..a8cba2d296bf 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts @@ -136,17 +136,19 @@ function getWordToLineNumbersMap(model: ITextModel | null): Map Date: Fri, 14 Feb 2020 14:42:16 +0100 Subject: [PATCH 046/109] Adjust minimap width based on effective font scale --- .../browser/viewParts/minimap/minimap.ts | 19 +++------ .../common/config/commonEditorConfig.ts | 9 ++-- src/vs/editor/common/config/editorOptions.ts | 41 +++++++++++++++++-- .../viewLayout/editorLayoutProvider.test.ts | 31 ++++++++++++++ src/vs/monaco.d.ts | 2 + 5 files changed, 81 insertions(+), 21 deletions(-) diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index 06a7f83f5fef..c82c8f5566df 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -128,31 +128,22 @@ class MinimapOptions { const baseCharHeight = (this.renderMinimap === RenderMinimap.Text ? Constants.BASE_CHAR_HEIGHT : Constants.BASE_CHAR_HEIGHT + 1); let fontScale: number; - let minimapLineHeight: number; - if (minimapOpts.entireDocument) { + const minimapLineHeight = layoutInfo.minimapLineHeight; + if (this.renderMinimap === RenderMinimap.None) { + fontScale = Math.round(minimapOpts.scale * pixelRatio); + } else if (minimapOpts.entireDocument) { if (isSampling) { fontScale = 1; - minimapLineHeight = 1; } else { + this.canvasInnerWidth = Math.floor(this.canvasInnerWidth * layoutInfo.minimapWidthMultiplier); const extraLinesBeyondLastLine = this.scrollBeyondLastLine ? (layoutInfo.height / this.lineHeight - 1) : 0; - const desiredRatio = (modelLineCount + extraLinesBeyondLastLine) / (pixelRatio * layoutInfo.height); - minimapLineHeight = Math.max(1, Math.floor(1 / desiredRatio)); - // fontScale = Math.round(minimapOpts.scale * pixelRatio); - - const configuredFontScale = Math.round(minimapOpts.scale * pixelRatio); fontScale = Math.min(4, Math.max(1, Math.floor(minimapLineHeight / baseCharHeight))); - const fontScaleRatio = fontScale / configuredFontScale; - if (fontScaleRatio > 1) { - this.canvasInnerWidth = Math.floor(this.canvasInnerWidth * fontScaleRatio); - } - const actualMinimapHeight = (modelLineCount + extraLinesBeyondLastLine) * minimapLineHeight; this.canvasInnerHeight = Math.ceil(actualMinimapHeight); } } else { fontScale = Math.round(minimapOpts.scale * pixelRatio); - minimapLineHeight = baseCharHeight * fontScale; } this.fontScale = fontScale; diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index ed8c20c1674b..7f488a873dc3 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -287,6 +287,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC public options!: ComputedEditorOptions; private _isDominatedByLongLines: boolean; + private _maxLineNumber: number; private _lineNumbersDigitCount: number; private _rawOptions: IEditorOptions; @@ -298,6 +299,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC this.isSimpleWidget = isSimpleWidget; this._isDominatedByLongLines = false; + this._maxLineNumber = 1; this._lineNumbersDigitCount = 1; this._rawOptions = deepCloneAndMigrateOptions(_options); @@ -347,6 +349,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC fontInfo: this.readConfiguration(bareFontInfo), extraEditorClassName: partialEnv.extraEditorClassName, isDominatedByLongLines: this._isDominatedByLongLines, + maxLineNumber: this._maxLineNumber, lineNumbersDigitCount: this._lineNumbersDigitCount, emptySelectionClipboard: partialEnv.emptySelectionClipboard, pixelRatio: partialEnv.pixelRatio, @@ -405,11 +408,11 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC } public setMaxLineNumber(maxLineNumber: number): void { - let digitCount = CommonEditorConfiguration._digitCount(maxLineNumber); - if (this._lineNumbersDigitCount === digitCount) { + if (this._maxLineNumber === maxLineNumber) { return; } - this._lineNumbersDigitCount = digitCount; + this._maxLineNumber = maxLineNumber; + this._lineNumbersDigitCount = CommonEditorConfiguration._digitCount(maxLineNumber); this._recomputeOptions(); } diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 29e137cf5d86..b99da7217830 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -8,6 +8,7 @@ import * as platform from 'vs/base/common/platform'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { FontInfo } from 'vs/editor/common/config/fontInfo'; import { Constants } from 'vs/base/common/uint'; +// import { Constants as MinimapConstants } from 'vs/editor/browser/viewParts/minimap/minimapCharSheet'; import { USUAL_WORD_SEPARATORS } from 'vs/editor/common/model/wordHelper'; import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; import { IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; @@ -672,6 +673,7 @@ export interface IEnvironmentalOptions { readonly fontInfo: FontInfo; readonly extraEditorClassName: string; readonly isDominatedByLongLines: boolean; + readonly maxLineNumber: number; readonly lineNumbersDigitCount: number; readonly emptySelectionClipboard: boolean; readonly pixelRatio: number; @@ -1685,6 +1687,8 @@ export interface EditorLayoutInfo { * The width of the minimap */ readonly minimapWidth: number; + readonly minimapLineHeight: number; + readonly minimapWidthMultiplier: number; /** * Minimap render type @@ -1718,6 +1722,7 @@ export interface EditorLayoutInfoComputerEnv { outerWidth: number; outerHeight: number; lineHeight: number; + maxLineNumber: number; lineNumbersDigitCount: number; typicalHalfwidthCharacterWidth: number; maxDigitWidth: number; @@ -1741,6 +1746,7 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption= 2 ? Math.round(minimap.scale * 2) : minimap.scale); + let minimapScale = (pixelRatio >= 2 ? Math.round(minimap.scale * 2) : minimap.scale); const minimapMaxColumn = minimap.maxColumn | 0; + const minimapEntireDocument = minimap.entireDocument; const scrollbar = options.get(EditorOption.scrollbar); const verticalScrollbarWidth = scrollbar.verticalScrollbarSize | 0; @@ -1805,19 +1813,42 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption 1) { + minimapScale = 1; + minimapLineHeight = 1; + minimapCharWidth = minimapScale / pixelRatio; + } else { + const configuredFontScale = minimapScale; + minimapLineHeight = Math.max(1, Math.floor(1 / desiredRatio)); + minimapScale = Math.min(4, Math.max(1, Math.floor(minimapLineHeight / baseCharHeight))); + minimapWidthMultiplier = Math.min(1, minimapScale / configuredFontScale); + minimapCharWidth = minimapScale / pixelRatio / minimapWidthMultiplier; + } + } + renderMinimap = minimapRenderCharacters ? RenderMinimap.Text : RenderMinimap.Blocks; // Given: @@ -1875,6 +1906,8 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption { outerWidth: input.outerWidth, outerHeight: input.outerHeight, lineHeight: input.lineHeight, + maxLineNumber: Math.pow(10, input.lineNumbersDigitCount) - 1, lineNumbersDigitCount: input.lineNumbersDigitCount, typicalHalfwidthCharacterWidth: input.typicalHalfwidthCharacterWidth, maxDigitWidth: input.maxDigitWidth, @@ -126,6 +127,8 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.None, minimapLeft: 0, minimapWidth: 0, + minimapLineHeight: 1, + minimapWidthMultiplier: 1, viewportColumn: 98, verticalScrollbarWidth: 0, @@ -180,6 +183,8 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.None, minimapLeft: 0, minimapWidth: 0, + minimapLineHeight: 1, + minimapWidthMultiplier: 1, viewportColumn: 97, verticalScrollbarWidth: 11, @@ -234,6 +239,8 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.None, minimapLeft: 0, minimapWidth: 0, + minimapLineHeight: 1, + minimapWidthMultiplier: 1, viewportColumn: 88, verticalScrollbarWidth: 0, @@ -288,6 +295,8 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.None, minimapLeft: 0, minimapWidth: 0, + minimapLineHeight: 1, + minimapWidthMultiplier: 1, viewportColumn: 88, verticalScrollbarWidth: 0, @@ -342,6 +351,8 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.None, minimapLeft: 0, minimapWidth: 0, + minimapLineHeight: 1, + minimapWidthMultiplier: 1, viewportColumn: 88, verticalScrollbarWidth: 0, @@ -396,6 +407,8 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.None, minimapLeft: 0, minimapWidth: 0, + minimapLineHeight: 1, + minimapWidthMultiplier: 1, viewportColumn: 83, verticalScrollbarWidth: 0, @@ -450,6 +463,8 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.None, minimapLeft: 0, minimapWidth: 0, + minimapLineHeight: 1, + minimapWidthMultiplier: 1, viewportColumn: 83, verticalScrollbarWidth: 0, @@ -504,6 +519,8 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.None, minimapLeft: 0, minimapWidth: 0, + minimapLineHeight: 1, + minimapWidthMultiplier: 1, viewportColumn: 82, verticalScrollbarWidth: 0, @@ -558,6 +575,8 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.None, minimapLeft: 0, minimapWidth: 0, + minimapLineHeight: 1, + minimapWidthMultiplier: 1, viewportColumn: 171, verticalScrollbarWidth: 0, @@ -612,6 +631,8 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.None, minimapLeft: 0, minimapWidth: 0, + minimapLineHeight: 1, + minimapWidthMultiplier: 1, viewportColumn: 169, verticalScrollbarWidth: 0, @@ -666,6 +687,8 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.Text, minimapLeft: 903, minimapWidth: 97, + minimapLineHeight: 2, + minimapWidthMultiplier: 1, viewportColumn: 89, verticalScrollbarWidth: 0, @@ -720,6 +743,8 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.Text, minimapLeft: 903, minimapWidth: 97, + minimapLineHeight: 4, + minimapWidthMultiplier: 1, viewportColumn: 89, verticalScrollbarWidth: 0, @@ -774,6 +799,8 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.Text, minimapLeft: 945, minimapWidth: 55, + minimapLineHeight: 4, + minimapWidthMultiplier: 1, viewportColumn: 93, verticalScrollbarWidth: 0, @@ -828,6 +855,8 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.Text, minimapLeft: 0, minimapWidth: 55, + minimapLineHeight: 4, + minimapWidthMultiplier: 1, viewportColumn: 93, verticalScrollbarWidth: 0, @@ -882,6 +911,8 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.Text, minimapLeft: 1096, minimapWidth: 91, + minimapLineHeight: 4, + minimapWidthMultiplier: 1, viewportColumn: 83, verticalScrollbarWidth: 14, diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index bd959e1267da..d234e6d91598 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3330,6 +3330,8 @@ declare namespace monaco.editor { * The width of the minimap */ readonly minimapWidth: number; + readonly minimapLineHeight: number; + readonly minimapWidthMultiplier: number; /** * Minimap render type */ From ee74224b8898d4f060a857b61dedb21467dd299e Mon Sep 17 00:00:00 2001 From: isidor Date: Fri, 14 Feb 2020 15:28:18 +0100 Subject: [PATCH 047/109] linux: automatically set force-renderer-accessibility when user sets editor.accessibilitySupport #90446 --- src/main.js | 3 ++- .../relauncher/browser/relauncher.contribution.ts | 12 +++++++++++- .../electron-browser/desktop.contribution.ts | 4 ++++ .../accessibility/node/accessibilityService.ts | 13 +++++++++++-- 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/main.js b/src/main.js index 7242b0cb156a..4bbbf263dab4 100644 --- a/src/main.js +++ b/src/main.js @@ -131,7 +131,8 @@ function configureCommandlineSwitchesSync(cliArgs) { 'disable-hardware-acceleration', // provided by Electron - 'disable-color-correct-rendering' + 'disable-color-correct-rendering', + 'force-renderer-accessibility' ]; // Read argv config diff --git a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts index a152b50423d7..8ff6aff23437 100644 --- a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts +++ b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts @@ -15,7 +15,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { RunOnceScheduler } from 'vs/base/common/async'; import { URI } from 'vs/base/common/uri'; import { isEqual } from 'vs/base/common/resources'; -import { isMacintosh, isNative } from 'vs/base/common/platform'; +import { isMacintosh, isNative, isLinux } from 'vs/base/common/platform'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -26,6 +26,7 @@ interface IConfiguration extends IWindowsConfiguration { telemetry: { enableCrashReporter: boolean }; workbench: { list: { horizontalScrolling: boolean } }; debug: { console: { wordWrap: boolean } }; + editor: { accessibilitySupport: 'on' | 'off' | 'auto' }; } export class SettingsChangeRelauncher extends Disposable implements IWorkbenchContribution { @@ -38,6 +39,7 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo private enableCrashReporter: boolean | undefined; private treeHorizontalScrolling: boolean | undefined; private debugConsoleWordWrap: boolean | undefined; + private accessibilitySupport: 'on' | 'off' | 'auto' | undefined; constructor( @IHostService private readonly hostService: IHostService, @@ -103,6 +105,14 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo this.enableCrashReporter = config.telemetry.enableCrashReporter; changed = true; } + + // On linux turning on accessibility support will also pass this flag to the chrome renderer, thus a restart is required + if (isLinux && typeof config.editor?.accessibilitySupport === 'string' && config.editor.accessibilitySupport !== this.accessibilitySupport) { + this.accessibilitySupport = config.editor.accessibilitySupport; + if (this.accessibilitySupport === 'on') { + changed = true; + } + } } // Notify only when changed and we are the focused window (avoids notification spam across windows) diff --git a/src/vs/workbench/electron-browser/desktop.contribution.ts b/src/vs/workbench/electron-browser/desktop.contribution.ts index 61e2e2e58850..4064bb05c052 100644 --- a/src/vs/workbench/electron-browser/desktop.contribution.ts +++ b/src/vs/workbench/electron-browser/desktop.contribution.ts @@ -341,6 +341,10 @@ import product from 'vs/platform/product/common/product'; 'disable-color-correct-rendering': { type: 'boolean', description: nls.localize('argv.disableColorCorrectRendering', 'Resolves issues around color profile selection. ONLY change this option if you encounter graphic issues.') + }, + 'force-renderer-accessibility': { + type: 'boolean', + description: nls.localize('argv.force-renderer-accessibility', 'Forces the renderer to be accessible. ONLY change this if you are using a screen reader on Linux. On other platforms the renderer will automatically be accessible. This flag is automatically set if you have editor.accessibilitySupport: on.') } } }); diff --git a/src/vs/workbench/services/accessibility/node/accessibilityService.ts b/src/vs/workbench/services/accessibility/node/accessibilityService.ts index faedc3fd3186..27c4d1249545 100644 --- a/src/vs/workbench/services/accessibility/node/accessibilityService.ts +++ b/src/vs/workbench/services/accessibility/node/accessibilityService.ts @@ -4,13 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; -import { isWindows } from 'vs/base/common/platform'; +import { isWindows, isLinux } from 'vs/base/common/platform'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { AccessibilityService } from 'vs/platform/accessibility/common/accessibilityService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; interface AccessibilityMetrics { enabled: boolean; @@ -29,10 +30,18 @@ export class NodeAccessibilityService extends AccessibilityService implements IA @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @IContextKeyService contextKeyService: IContextKeyService, @IConfigurationService configurationService: IConfigurationService, - @ITelemetryService private readonly _telemetryService: ITelemetryService + @ITelemetryService private readonly _telemetryService: ITelemetryService, + @IJSONEditingService jsonEditingService: IJSONEditingService ) { super(contextKeyService, configurationService); this.setAccessibilitySupport(environmentService.configuration.accessibilitySupport ? AccessibilitySupport.Enabled : AccessibilitySupport.Disabled); + if (isLinux) { + this._register(this.onDidChangeScreenReaderOptimized(async () => { + if (this.isScreenReaderOptimized()) { + await jsonEditingService.write(environmentService.argvResource, [{ key: 'force-renderer-accessibility', value: true }], true); + } + })); + } } alwaysUnderlineAccessKeys(): Promise { From f4df26f80ef5f4d74778e3548960f5f342bde934 Mon Sep 17 00:00:00 2001 From: isidor Date: Fri, 14 Feb 2020 15:30:52 +0100 Subject: [PATCH 048/109] Revert "linux: automatically set force-renderer-accessibility when user sets editor.accessibilitySupport" This reverts commit ee74224b8898d4f060a857b61dedb21467dd299e. --- src/main.js | 3 +-- .../relauncher/browser/relauncher.contribution.ts | 12 +----------- .../electron-browser/desktop.contribution.ts | 4 ---- .../accessibility/node/accessibilityService.ts | 13 ++----------- 4 files changed, 4 insertions(+), 28 deletions(-) diff --git a/src/main.js b/src/main.js index 4bbbf263dab4..7242b0cb156a 100644 --- a/src/main.js +++ b/src/main.js @@ -131,8 +131,7 @@ function configureCommandlineSwitchesSync(cliArgs) { 'disable-hardware-acceleration', // provided by Electron - 'disable-color-correct-rendering', - 'force-renderer-accessibility' + 'disable-color-correct-rendering' ]; // Read argv config diff --git a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts index 8ff6aff23437..a152b50423d7 100644 --- a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts +++ b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts @@ -15,7 +15,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { RunOnceScheduler } from 'vs/base/common/async'; import { URI } from 'vs/base/common/uri'; import { isEqual } from 'vs/base/common/resources'; -import { isMacintosh, isNative, isLinux } from 'vs/base/common/platform'; +import { isMacintosh, isNative } from 'vs/base/common/platform'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -26,7 +26,6 @@ interface IConfiguration extends IWindowsConfiguration { telemetry: { enableCrashReporter: boolean }; workbench: { list: { horizontalScrolling: boolean } }; debug: { console: { wordWrap: boolean } }; - editor: { accessibilitySupport: 'on' | 'off' | 'auto' }; } export class SettingsChangeRelauncher extends Disposable implements IWorkbenchContribution { @@ -39,7 +38,6 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo private enableCrashReporter: boolean | undefined; private treeHorizontalScrolling: boolean | undefined; private debugConsoleWordWrap: boolean | undefined; - private accessibilitySupport: 'on' | 'off' | 'auto' | undefined; constructor( @IHostService private readonly hostService: IHostService, @@ -105,14 +103,6 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo this.enableCrashReporter = config.telemetry.enableCrashReporter; changed = true; } - - // On linux turning on accessibility support will also pass this flag to the chrome renderer, thus a restart is required - if (isLinux && typeof config.editor?.accessibilitySupport === 'string' && config.editor.accessibilitySupport !== this.accessibilitySupport) { - this.accessibilitySupport = config.editor.accessibilitySupport; - if (this.accessibilitySupport === 'on') { - changed = true; - } - } } // Notify only when changed and we are the focused window (avoids notification spam across windows) diff --git a/src/vs/workbench/electron-browser/desktop.contribution.ts b/src/vs/workbench/electron-browser/desktop.contribution.ts index 4064bb05c052..61e2e2e58850 100644 --- a/src/vs/workbench/electron-browser/desktop.contribution.ts +++ b/src/vs/workbench/electron-browser/desktop.contribution.ts @@ -341,10 +341,6 @@ import product from 'vs/platform/product/common/product'; 'disable-color-correct-rendering': { type: 'boolean', description: nls.localize('argv.disableColorCorrectRendering', 'Resolves issues around color profile selection. ONLY change this option if you encounter graphic issues.') - }, - 'force-renderer-accessibility': { - type: 'boolean', - description: nls.localize('argv.force-renderer-accessibility', 'Forces the renderer to be accessible. ONLY change this if you are using a screen reader on Linux. On other platforms the renderer will automatically be accessible. This flag is automatically set if you have editor.accessibilitySupport: on.') } } }); diff --git a/src/vs/workbench/services/accessibility/node/accessibilityService.ts b/src/vs/workbench/services/accessibility/node/accessibilityService.ts index 27c4d1249545..faedc3fd3186 100644 --- a/src/vs/workbench/services/accessibility/node/accessibilityService.ts +++ b/src/vs/workbench/services/accessibility/node/accessibilityService.ts @@ -4,14 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; -import { isWindows, isLinux } from 'vs/base/common/platform'; +import { isWindows } from 'vs/base/common/platform'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { AccessibilityService } from 'vs/platform/accessibility/common/accessibilityService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; interface AccessibilityMetrics { enabled: boolean; @@ -30,18 +29,10 @@ export class NodeAccessibilityService extends AccessibilityService implements IA @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @IContextKeyService contextKeyService: IContextKeyService, @IConfigurationService configurationService: IConfigurationService, - @ITelemetryService private readonly _telemetryService: ITelemetryService, - @IJSONEditingService jsonEditingService: IJSONEditingService + @ITelemetryService private readonly _telemetryService: ITelemetryService ) { super(contextKeyService, configurationService); this.setAccessibilitySupport(environmentService.configuration.accessibilitySupport ? AccessibilitySupport.Enabled : AccessibilitySupport.Disabled); - if (isLinux) { - this._register(this.onDidChangeScreenReaderOptimized(async () => { - if (this.isScreenReaderOptimized()) { - await jsonEditingService.write(environmentService.argvResource, [{ key: 'force-renderer-accessibility', value: true }], true); - } - })); - } } alwaysUnderlineAccessKeys(): Promise { From 798baf0534a16e63400384fd95d9a62accc60ed9 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 14 Feb 2020 15:37:07 +0100 Subject: [PATCH 049/109] Move more minimap layout to EditorLayoutInfoComputer --- .../browser/viewParts/minimap/minimap.ts | 44 +++++-------------- src/vs/editor/common/config/editorOptions.ts | 29 ++++++++++-- src/vs/monaco.d.ts | 7 ++- 3 files changed, 43 insertions(+), 37 deletions(-) diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index c82c8f5566df..b8526ae9e4d6 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -91,12 +91,12 @@ class MinimapOptions { */ public readonly canvasOuterHeight: number; - public readonly backgroundColor: RGBA8; - public readonly fontScale: number; public readonly minimapLineHeight: number; public readonly minimapCharWidth: number; + public readonly charRenderer: () => MinimapCharRenderer; + public readonly backgroundColor: RGBA8; constructor(configuration: IConfiguration, theme: EditorTheme, tokensColorTracker: MinimapTokensColorTracker, modelLineCount: number, isSampling: boolean) { const options = configuration.options; @@ -117,39 +117,17 @@ class MinimapOptions { this.minimapWidth = layoutInfo.minimapWidth; this.minimapHeight = layoutInfo.height; - this.canvasInnerWidth = Math.floor(pixelRatio * this.minimapWidth); - this.canvasInnerHeight = Math.floor(pixelRatio * this.minimapHeight); + this.canvasInnerWidth = layoutInfo.minimapCanvasInnerWidth; + this.canvasInnerHeight = layoutInfo.minimapCanvasInnerHeight; + this.canvasOuterWidth = layoutInfo.minimapCanvasOuterWidth; + this.canvasOuterHeight = layoutInfo.minimapCanvasOuterHeight; - this.canvasOuterWidth = this.canvasInnerWidth / pixelRatio; - this.canvasOuterHeight = this.canvasInnerHeight / pixelRatio; - - this.backgroundColor = MinimapOptions._getMinimapBackground(theme, tokensColorTracker); - - const baseCharHeight = (this.renderMinimap === RenderMinimap.Text ? Constants.BASE_CHAR_HEIGHT : Constants.BASE_CHAR_HEIGHT + 1); - - let fontScale: number; - const minimapLineHeight = layoutInfo.minimapLineHeight; - if (this.renderMinimap === RenderMinimap.None) { - fontScale = Math.round(minimapOpts.scale * pixelRatio); - } else if (minimapOpts.entireDocument) { - if (isSampling) { - fontScale = 1; - } else { - this.canvasInnerWidth = Math.floor(this.canvasInnerWidth * layoutInfo.minimapWidthMultiplier); - const extraLinesBeyondLastLine = this.scrollBeyondLastLine ? (layoutInfo.height / this.lineHeight - 1) : 0; - fontScale = Math.min(4, Math.max(1, Math.floor(minimapLineHeight / baseCharHeight))); - - const actualMinimapHeight = (modelLineCount + extraLinesBeyondLastLine) * minimapLineHeight; - this.canvasInnerHeight = Math.ceil(actualMinimapHeight); - } - } else { - fontScale = Math.round(minimapOpts.scale * pixelRatio); - } - - this.fontScale = fontScale; - this.minimapLineHeight = minimapLineHeight; + this.fontScale = layoutInfo.minimapScale; + this.minimapLineHeight = layoutInfo.minimapLineHeight; this.minimapCharWidth = Constants.BASE_CHAR_WIDTH * this.fontScale; + this.charRenderer = once(() => MinimapCharRendererFactory.create(this.fontScale, fontInfo.fontFamily)); + this.backgroundColor = MinimapOptions._getMinimapBackground(theme, tokensColorTracker); } private static _getMinimapBackground(theme: EditorTheme, tokensColorTracker: MinimapTokensColorTracker): RGBA8 { @@ -176,10 +154,10 @@ class MinimapOptions { && this.canvasInnerHeight === other.canvasInnerHeight && this.canvasOuterWidth === other.canvasOuterWidth && this.canvasOuterHeight === other.canvasOuterHeight - && this.backgroundColor.equals(other.backgroundColor) && this.fontScale === other.fontScale && this.minimapLineHeight === other.minimapLineHeight && this.minimapCharWidth === other.minimapCharWidth + && this.backgroundColor.equals(other.backgroundColor) ); } } diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index b99da7217830..b312fd966fdb 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -1687,8 +1687,13 @@ export interface EditorLayoutInfo { * The width of the minimap */ readonly minimapWidth: number; + readonly minimapIsSampling: boolean; + readonly minimapScale: number; readonly minimapLineHeight: number; - readonly minimapWidthMultiplier: number; + readonly minimapCanvasInnerWidth: number; + readonly minimapCanvasInnerHeight: number; + readonly minimapCanvasOuterWidth: number; + readonly minimapCanvasOuterHeight: number; /** * Minimap render type @@ -1817,17 +1822,24 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption 1) { + minimapIsSampling = true; minimapScale = 1; minimapLineHeight = 1; minimapCharWidth = minimapScale / pixelRatio; @@ -1846,6 +1859,7 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption Date: Fri, 14 Feb 2020 15:42:25 +0100 Subject: [PATCH 050/109] linux: automatically set force-renderer-accessibility when user sets editor.accessibilitySupport --- src/main.js | 3 ++- .../browser/relauncher.contribution.ts | 12 ++++++++- .../electron-browser/desktop.contribution.ts | 4 +++ .../node/accessibilityService.ts | 26 ++++++++++++++++++- 4 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/main.js b/src/main.js index 7242b0cb156a..4bbbf263dab4 100644 --- a/src/main.js +++ b/src/main.js @@ -131,7 +131,8 @@ function configureCommandlineSwitchesSync(cliArgs) { 'disable-hardware-acceleration', // provided by Electron - 'disable-color-correct-rendering' + 'disable-color-correct-rendering', + 'force-renderer-accessibility' ]; // Read argv config diff --git a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts index a152b50423d7..8ff6aff23437 100644 --- a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts +++ b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts @@ -15,7 +15,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { RunOnceScheduler } from 'vs/base/common/async'; import { URI } from 'vs/base/common/uri'; import { isEqual } from 'vs/base/common/resources'; -import { isMacintosh, isNative } from 'vs/base/common/platform'; +import { isMacintosh, isNative, isLinux } from 'vs/base/common/platform'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -26,6 +26,7 @@ interface IConfiguration extends IWindowsConfiguration { telemetry: { enableCrashReporter: boolean }; workbench: { list: { horizontalScrolling: boolean } }; debug: { console: { wordWrap: boolean } }; + editor: { accessibilitySupport: 'on' | 'off' | 'auto' }; } export class SettingsChangeRelauncher extends Disposable implements IWorkbenchContribution { @@ -38,6 +39,7 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo private enableCrashReporter: boolean | undefined; private treeHorizontalScrolling: boolean | undefined; private debugConsoleWordWrap: boolean | undefined; + private accessibilitySupport: 'on' | 'off' | 'auto' | undefined; constructor( @IHostService private readonly hostService: IHostService, @@ -103,6 +105,14 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo this.enableCrashReporter = config.telemetry.enableCrashReporter; changed = true; } + + // On linux turning on accessibility support will also pass this flag to the chrome renderer, thus a restart is required + if (isLinux && typeof config.editor?.accessibilitySupport === 'string' && config.editor.accessibilitySupport !== this.accessibilitySupport) { + this.accessibilitySupport = config.editor.accessibilitySupport; + if (this.accessibilitySupport === 'on') { + changed = true; + } + } } // Notify only when changed and we are the focused window (avoids notification spam across windows) diff --git a/src/vs/workbench/electron-browser/desktop.contribution.ts b/src/vs/workbench/electron-browser/desktop.contribution.ts index 61e2e2e58850..4064bb05c052 100644 --- a/src/vs/workbench/electron-browser/desktop.contribution.ts +++ b/src/vs/workbench/electron-browser/desktop.contribution.ts @@ -341,6 +341,10 @@ import product from 'vs/platform/product/common/product'; 'disable-color-correct-rendering': { type: 'boolean', description: nls.localize('argv.disableColorCorrectRendering', 'Resolves issues around color profile selection. ONLY change this option if you encounter graphic issues.') + }, + 'force-renderer-accessibility': { + type: 'boolean', + description: nls.localize('argv.force-renderer-accessibility', 'Forces the renderer to be accessible. ONLY change this if you are using a screen reader on Linux. On other platforms the renderer will automatically be accessible. This flag is automatically set if you have editor.accessibilitySupport: on.') } } }); diff --git a/src/vs/workbench/services/accessibility/node/accessibilityService.ts b/src/vs/workbench/services/accessibility/node/accessibilityService.ts index faedc3fd3186..a2911ba04a57 100644 --- a/src/vs/workbench/services/accessibility/node/accessibilityService.ts +++ b/src/vs/workbench/services/accessibility/node/accessibilityService.ts @@ -4,13 +4,18 @@ *--------------------------------------------------------------------------------------------*/ import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; -import { isWindows } from 'vs/base/common/platform'; +import { isWindows, isLinux } from 'vs/base/common/platform'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { Registry } from 'vs/platform/registry/common/platform'; import { AccessibilityService } from 'vs/platform/accessibility/common/accessibilityService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; +import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; interface AccessibilityMetrics { enabled: boolean; @@ -65,3 +70,22 @@ export class NodeAccessibilityService extends AccessibilityService implements IA } registerSingleton(IAccessibilityService, NodeAccessibilityService, true); + +// On linux we do not automatically detect that a screen reader is detected, thus we have to implicitly notify the renderer to enable accessibility when user configures it in settings +class LinuxAccessibilityContribution implements IWorkbenchContribution { + constructor( + @IJSONEditingService jsonEditingService: IJSONEditingService, + @IAccessibilityService accessibilityService: AccessibilityService, + @IEnvironmentService environmentService: IEnvironmentService + ) { + accessibilityService.onDidChangeScreenReaderOptimized(async () => { + if (accessibilityService.isScreenReaderOptimized()) { + await jsonEditingService.write(environmentService.argvResource, [{ key: 'force-renderer-accessibility', value: true }], true); + } + }); + } +} + +if (isLinux) { + Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(LinuxAccessibilityContribution, LifecyclePhase.Ready); +} From 6e2782fa5a2d7e511b1c5dfd8b11474df9d2b884 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 14 Feb 2020 15:58:25 +0100 Subject: [PATCH 051/109] fix https://github.com/microsoft/vscode/issues/90697 --- .../contrib/outline/browser/outlinePane.ts | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/outline/browser/outlinePane.ts b/src/vs/workbench/contrib/outline/browser/outlinePane.ts index 7214dddd208c..c4ba64d1e82c 100644 --- a/src/vs/workbench/contrib/outline/browser/outlinePane.ts +++ b/src/vs/workbench/contrib/outline/browser/outlinePane.ts @@ -27,7 +27,7 @@ import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IResourceInput, TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor'; +import { TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { WorkbenchDataTree } from 'vs/platform/list/browser/listService'; @@ -37,7 +37,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { CollapseAction } from 'vs/workbench/browser/viewlet'; -import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { OutlineConfigKeys, OutlineViewFocused, OutlineViewFiltered } from 'vs/editor/contrib/documentSymbols/outline'; import { FuzzyScore } from 'vs/base/common/filters'; import { OutlineDataSource, OutlineItemComparator, OutlineSortOrder, OutlineVirtualDelegate, OutlineGroupRenderer, OutlineElementRenderer, OutlineItem, OutlineIdentityProvider, OutlineNavigationLabelProvider, OutlineFilter } from 'vs/editor/contrib/documentSymbols/outlineTree'; @@ -49,6 +49,7 @@ import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDeco import { MarkerSeverity } from 'vs/platform/markers/common/markers'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; class RequestState { @@ -261,7 +262,7 @@ export class OutlinePane extends ViewPane { @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IThemeService private readonly _themeService: IThemeService, @IStorageService private readonly _storageService: IStorageService, - @IEditorService private readonly _editorService: IEditorService, + @ICodeEditorService private readonly _editorService: ICodeEditorService, @IMarkerDecorationsService private readonly _markerDecorationService: IMarkerDecorationsService, @IConfigurationService private readonly _configurationService: IConfigurationService, @IKeybindingService keybindingService: IKeybindingService, @@ -347,7 +348,7 @@ export class OutlinePane extends ViewPane { this._disposables.push(this._tree); this._disposables.push(this._outlineViewState.onDidChange(this._onDidChangeUserState, this)); - this._disposables.push(this.viewDescriptorService.onDidChangeLocation(({ views, from, to }) => { + this._disposables.push(this.viewDescriptorService.onDidChangeLocation(({ views }) => { if (views.some(v => v.id === this.id)) { this._tree.updateOptions({ overrideStyles: { listBackground: this.getBackgroundColor() } }); } @@ -629,15 +630,18 @@ export class OutlinePane extends ViewPane { } private async _revealTreeSelection(model: OutlineModel, element: OutlineElement, focus: boolean, aside: boolean): Promise { - - await this._editorService.openEditor({ - resource: model.textModel.uri, - options: { - preserveFocus: !focus, - selection: Range.collapseToStart(element.symbol.selectionRange), - selectionRevealType: TextEditorSelectionRevealType.NearTop, - } - } as IResourceInput, aside ? SIDE_GROUP : ACTIVE_GROUP); + await this._editorService.openCodeEditor( + { + resource: model.textModel.uri, + options: { + preserveFocus: !focus, + selection: Range.collapseToStart(element.symbol.selectionRange), + selectionRevealType: TextEditorSelectionRevealType.NearTop, + } + }, + this._editorService.getActiveCodeEditor(), + aside + ); } private _revealEditorSelection(model: OutlineModel, selection: Selection): void { From 5970b514342781984475e21d0d81c212c9f97afa Mon Sep 17 00:00:00 2001 From: isidor Date: Fri, 14 Feb 2020 16:04:33 +0100 Subject: [PATCH 052/109] only allow forceRendererAccessibility flag on linux --- src/main.js | 6 ++++-- .../electron-browser/desktop.contribution.ts | 18 +++++++++++------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/main.js b/src/main.js index 4bbbf263dab4..0e90f4504c83 100644 --- a/src/main.js +++ b/src/main.js @@ -131,9 +131,11 @@ function configureCommandlineSwitchesSync(cliArgs) { 'disable-hardware-acceleration', // provided by Electron - 'disable-color-correct-rendering', - 'force-renderer-accessibility' + 'disable-color-correct-rendering' ]; + if (process.platform === 'linux') { + SUPPORTED_ELECTRON_SWITCHES.push('force-renderer-accessibility'); + } // Read argv config const argvConfig = readArgvConfigSync(); diff --git a/src/vs/workbench/electron-browser/desktop.contribution.ts b/src/vs/workbench/electron-browser/desktop.contribution.ts index 4064bb05c052..f1d8f9af2533 100644 --- a/src/vs/workbench/electron-browser/desktop.contribution.ts +++ b/src/vs/workbench/electron-browser/desktop.contribution.ts @@ -22,6 +22,7 @@ import { NoEditorsVisibleContext, SingleEditorGroupsContext } from 'vs/workbench import { IElectronService } from 'vs/platform/electron/node/electron'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import product from 'vs/platform/product/common/product'; +import { IJSONSchema } from 'vs/base/common/jsonSchema'; // Actions (function registerActions(): void { @@ -321,8 +322,7 @@ import product from 'vs/platform/product/common/product'; (function registerJSONSchemas(): void { const argvDefinitionFileSchemaId = 'vscode://schemas/argv'; const jsonRegistry = Registry.as(JSONExtensions.JSONContribution); - - jsonRegistry.registerSchema(argvDefinitionFileSchemaId, { + const schema: IJSONSchema = { id: argvDefinitionFileSchemaId, allowComments: true, allowTrailingCommas: true, @@ -341,11 +341,15 @@ import product from 'vs/platform/product/common/product'; 'disable-color-correct-rendering': { type: 'boolean', description: nls.localize('argv.disableColorCorrectRendering', 'Resolves issues around color profile selection. ONLY change this option if you encounter graphic issues.') - }, - 'force-renderer-accessibility': { - type: 'boolean', - description: nls.localize('argv.force-renderer-accessibility', 'Forces the renderer to be accessible. ONLY change this if you are using a screen reader on Linux. On other platforms the renderer will automatically be accessible. This flag is automatically set if you have editor.accessibilitySupport: on.') } } - }); + }; + if (isLinux) { + schema.properties!['force-renderer-accessibility'] = { + type: 'boolean', + description: nls.localize('argv.force-renderer-accessibility', 'Forces the renderer to be accessible. ONLY change this if you are using a screen reader on Linux. On other platforms the renderer will automatically be accessible. This flag is automatically set if you have editor.accessibilitySupport: on.'), + }; + } + + jsonRegistry.registerSchema(argvDefinitionFileSchemaId, schema); })(); From 59ac0a8e57ab35245f3ca2d49eff1772a1714940 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 14 Feb 2020 07:09:45 -0800 Subject: [PATCH 053/109] Fix typo --- .../platform/userDataSync/common/userDataAutoSyncService.ts | 2 +- src/vs/workbench/common/views.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts b/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts index 7ebe69a2c9c7..e4d1c0ae4f8b 100644 --- a/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts @@ -98,7 +98,7 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto async triggerAutoSync(): Promise { if (this.enabled) { return this.syncDelayer.trigger(() => { - this.logService.info('Auto Sync: Triggerred.'); + this.logService.info('Auto Sync: Triggered.'); return this.sync(false, true); }, this.successiveFailures ? 1000 * 1 * Math.min(this.successiveFailures, 60) /* Delay by number of seconds as number of failures up to 1 minute */ diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 0a6073ca70b6..1620263f6cbb 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -57,12 +57,12 @@ export interface IViewContainerDescriptor { export interface IViewContainersRegistry { /** - * An event that is triggerred when a view container is registered. + * An event that is triggered when a view container is registered. */ readonly onDidRegister: Event<{ viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation }>; /** - * An event that is triggerred when a view container is deregistered. + * An event that is triggered when a view container is deregistered. */ readonly onDidDeregister: Event<{ viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation }>; From 6f4962b27b7a437cdc73172b1f789adc1c5c4a55 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 14 Feb 2020 16:33:39 +0100 Subject: [PATCH 054/109] Clean up minimap sampling logic --- .../browser/viewParts/minimap/minimap.ts | 368 ++++++++---------- src/vs/editor/common/config/editorOptions.ts | 26 +- 2 files changed, 194 insertions(+), 200 deletions(-) diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index b8526ae9e4d6..8301788bdc99 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -13,7 +13,7 @@ import * as platform from 'vs/base/common/platform'; import * as strings from 'vs/base/common/strings'; import { ILine, RenderedLinesCollection } from 'vs/editor/browser/view/viewLayer'; import { PartFingerprint, PartFingerprints, ViewPart } from 'vs/editor/browser/view/viewPart'; -import { RenderMinimap, EditorOption, MINIMAP_GUTTER_WIDTH } from 'vs/editor/common/config/editorOptions'; +import { RenderMinimap, EditorOption, MINIMAP_GUTTER_WIDTH, EditorLayoutInfoComputer, IComputedEditorOptions } from 'vs/editor/common/config/editorOptions'; import { Range } from 'vs/editor/common/core/range'; import { RGBA8 } from 'vs/editor/common/core/rgba'; import { IConfiguration, ScrollType } from 'vs/editor/common/editorCommon'; @@ -24,7 +24,7 @@ import { MinimapTokensColorTracker } from 'vs/editor/common/viewModel/minimapTok import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/view/renderingContext'; import { ViewContext, EditorTheme } from 'vs/editor/common/view/viewContext'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; -import { ViewLineData, ViewModelDecoration, IViewModel } from 'vs/editor/common/viewModel/viewModel'; +import { ViewLineData, ViewModelDecoration } from 'vs/editor/common/viewModel/viewModel'; import { minimapSelection, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, minimapBackground } from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { ModelDecorationMinimapOptions } from 'vs/editor/common/model/textModel'; @@ -44,8 +44,6 @@ const GUTTER_DECORATION_WIDTH = 2; class MinimapOptions { - public readonly isSampling: boolean; - public readonly renderMinimap: RenderMinimap; public readonly entireDocument: boolean; @@ -98,14 +96,13 @@ class MinimapOptions { public readonly charRenderer: () => MinimapCharRenderer; public readonly backgroundColor: RGBA8; - constructor(configuration: IConfiguration, theme: EditorTheme, tokensColorTracker: MinimapTokensColorTracker, modelLineCount: number, isSampling: boolean) { + constructor(configuration: IConfiguration, theme: EditorTheme, tokensColorTracker: MinimapTokensColorTracker) { const options = configuration.options; const pixelRatio = options.get(EditorOption.pixelRatio); const layoutInfo = options.get(EditorOption.layoutInfo); const fontInfo = options.get(EditorOption.fontInfo); const minimapOpts = options.get(EditorOption.minimap); - this.isSampling = isSampling; this.renderMinimap = layoutInfo.renderMinimap | 0; this.entireDocument = minimapOpts.entireDocument; this.scrollBeyondLastLine = options.get(EditorOption.scrollBeyondLastLine); @@ -139,8 +136,7 @@ class MinimapOptions { } public equals(other: MinimapOptions): boolean { - return (this.isSampling === other.isSampling - && this.renderMinimap === other.renderMinimap + return (this.renderMinimap === other.renderMinimap && this.entireDocument === other.entireDocument && this.scrollBeyondLastLine === other.scrollBeyondLastLine && this.showSlider === other.showSlider @@ -523,42 +519,41 @@ type SamplingStateEvent = SamplingStateLinesInsertedEvent | SamplingStateLinesDe class MinimapSamplingState { - public static compute(configuration: IConfiguration, model: IViewModel, oldMinimapLines: number[] | null): [MinimapSamplingState, SamplingStateEvent[]] { - const options = configuration.options; + public static compute(options: IComputedEditorOptions, modelLineCount: number, oldSamplingState: MinimapSamplingState | null): [MinimapSamplingState | null, SamplingStateEvent[]] { const minimapOpts = options.get(EditorOption.minimap); - if (minimapOpts.enabled === false || !minimapOpts.entireDocument) { - return [new MinimapSamplingState(false, 1, []), []]; + const layoutInfo = options.get(EditorOption.layoutInfo); + if (!minimapOpts.enabled || !layoutInfo.minimapIsSampling) { + return [null, []]; } - const layoutInfo = options.get(EditorOption.layoutInfo); + // ratio is intentionally not part of the layout to avoid the layout changing all the time + // so we need to recompute it again... const pixelRatio = options.get(EditorOption.pixelRatio); const lineHeight = options.get(EditorOption.lineHeight); const scrollBeyondLastLine = options.get(EditorOption.scrollBeyondLastLine); - const modelLineCount = model.getLineCount(); - - const extraLinesBeyondLastLine = scrollBeyondLastLine ? (layoutInfo.height / lineHeight - 1) : 0; - const desiredRatio = (modelLineCount + extraLinesBeyondLastLine) / (pixelRatio * layoutInfo.height); - const minimapLineCount = Math.floor(modelLineCount / desiredRatio); + const { minimapLineCount } = EditorLayoutInfoComputer.computeEntireDocumentMinimapLineCount({ + modelLineCount: modelLineCount, + scrollBeyondLastLine: scrollBeyondLastLine, + height: layoutInfo.height, + lineHeight: lineHeight, + pixelRatio: pixelRatio + }); const ratio = modelLineCount / minimapLineCount; + const halfRatio = ratio / 2; - if (ratio <= 1) { - return [new MinimapSamplingState(false, 1, []), []]; - } - - if (!oldMinimapLines || oldMinimapLines.length === 0) { + if (!oldSamplingState || oldSamplingState.minimapLines.length === 0) { let result: number[] = []; result[0] = 1; if (minimapLineCount > 1) { - const halfRatio = ratio / 2; for (let i = 0, lastIndex = minimapLineCount - 1; i < lastIndex; i++) { result[i] = Math.round(i * ratio + halfRatio); } result[minimapLineCount - 1] = modelLineCount; } - return [new MinimapSamplingState(true, ratio, result), []]; + return [new MinimapSamplingState(ratio, result), []]; } - const halfRatio = ratio / 2; + const oldMinimapLines = oldSamplingState.minimapLines; const oldLength = oldMinimapLines.length; let result: number[] = []; let oldIndex = 0; @@ -631,46 +626,118 @@ class MinimapSamplingState { events = [{ type: 'flush' }]; } - return [new MinimapSamplingState(true, ratio, result), events]; + return [new MinimapSamplingState(ratio, result), events]; } constructor( - public readonly isSampling: boolean, public readonly samplingRatio: number, public readonly minimapLines: number[] ) { } + + public modelLineToMinimapLine(lineNumber: number): number { + return Math.min(this.minimapLines.length, Math.max(1, Math.round(lineNumber / this.samplingRatio))); + } + + /** + * Will return null if the model line ranges are not intersecting with a sampled model line. + */ + public modelLineRangeToMinimapLineRange(fromLineNumber: number, toLineNumber: number): [number, number] | null { + let fromLineIndex = this.modelLineToMinimapLine(fromLineNumber) - 1; + while (fromLineIndex > 0 && this.minimapLines[fromLineIndex - 1] >= fromLineNumber) { + fromLineIndex--; + } + let toLineIndex = this.modelLineToMinimapLine(toLineNumber) - 1; + while (toLineIndex + 1 < this.minimapLines.length && this.minimapLines[toLineIndex + 1] <= toLineNumber) { + toLineIndex++; + } + if (fromLineIndex === toLineIndex) { + const sampledLineNumber = this.minimapLines[fromLineIndex]; + if (sampledLineNumber < fromLineNumber || sampledLineNumber > toLineNumber) { + // This line is not part of the sampled lines ==> nothing to do + return null; + } + } + return [fromLineIndex + 1, toLineIndex + 1]; + } + + /** + * Will always return a range, even if it is not intersecting with a sampled model line. + */ + public decorationLineRangeToMinimapLineRange(startLineNumber: number, endLineNumber: number): [number, number] { + let minimapLineStart = this.modelLineToMinimapLine(startLineNumber); + let minimapLineEnd = this.modelLineToMinimapLine(endLineNumber); + if (startLineNumber !== endLineNumber && minimapLineEnd === minimapLineStart) { + if (minimapLineEnd === this.minimapLines.length) { + if (minimapLineStart > 1) { + minimapLineStart--; + } + } else { + minimapLineEnd++; + } + } + return [minimapLineStart, minimapLineEnd]; + } + + public onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): [number, number] { + // have the mapping be sticky + const deletedLineCount = e.toLineNumber - e.fromLineNumber + 1; + let changeStartIndex = this.minimapLines.length; + let changeEndIndex = 0; + for (let i = this.minimapLines.length - 1; i >= 0; i--) { + if (this.minimapLines[i] < e.fromLineNumber) { + break; + } + if (this.minimapLines[i] <= e.toLineNumber) { + // this line got deleted => move to previous available + this.minimapLines[i] = Math.max(1, e.fromLineNumber - 1); + changeStartIndex = Math.min(changeStartIndex, i); + changeEndIndex = Math.max(changeEndIndex, i); + } else { + this.minimapLines[i] -= deletedLineCount; + } + } + return [changeStartIndex, changeEndIndex]; + } + + public onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): void { + // have the mapping be sticky + const insertedLineCount = e.toLineNumber - e.fromLineNumber + 1; + for (let i = this.minimapLines.length - 1; i >= 0; i--) { + if (this.minimapLines[i] < e.fromLineNumber) { + break; + } + this.minimapLines[i] += insertedLineCount; + } + } } export class Minimap extends ViewPart implements IMinimapModel { - private _selections: Selection[]; - private _minimapSelections: Selection[]; - - private _isSampling: boolean; - private _samplingRatio: number; - private _minimapLines: number[]; - private _shouldCheckSampling: boolean; - public readonly tokensColorTracker: MinimapTokensColorTracker; + + private _selections: Selection[]; + private _minimapSelections: Selection[] | null; + public options: MinimapOptions; + private _samplingState: MinimapSamplingState | null; + private _shouldCheckSampling: boolean; + private _actual: InnerMinimap; constructor(context: ViewContext) { super(context); - this._selections = []; - this._minimapSelections = []; - - const [samplingState,] = MinimapSamplingState.compute(this._context.configuration, this._context.model, null); - this._isSampling = samplingState.isSampling; - this._samplingRatio = samplingState.samplingRatio; - this._minimapLines = samplingState.minimapLines; - this._shouldCheckSampling = false; - this.tokensColorTracker = MinimapTokensColorTracker.getInstance(); - this.options = new MinimapOptions(this._context.configuration, this._context.theme, this.tokensColorTracker, this._context.model.getLineCount(), this._isSampling); + + this._selections = []; + this._minimapSelections = null; + + this.options = new MinimapOptions(this._context.configuration, this._context.theme, this.tokensColorTracker); + const [samplingState,] = MinimapSamplingState.compute(this._context.configuration.options, this._context.model.getLineCount(), null); + this._samplingState = samplingState; + this._shouldCheckSampling = false; this._actual = new InnerMinimap(context.theme, this); } @@ -685,11 +752,12 @@ export class Minimap extends ViewPart implements IMinimapModel { } private _onOptionsMaybeChanged(): boolean { - const opts = new MinimapOptions(this._context.configuration, this._context.theme, this.tokensColorTracker, this._context.model.getLineCount(), this._isSampling); + const opts = new MinimapOptions(this._context.configuration, this._context.theme, this.tokensColorTracker); if (this.options.equals(opts)) { return false; } this.options = opts; + this._recreateLineSampling(); this._actual.onDidChangeOptions(); return true; } @@ -697,14 +765,11 @@ export class Minimap extends ViewPart implements IMinimapModel { // ---- begin view event handlers public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { - let shouldRender = false; - shouldRender = this._recreateLineSampling() || shouldRender; - shouldRender = this._onOptionsMaybeChanged() || shouldRender; - return shouldRender; + return this._onOptionsMaybeChanged(); } public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { this._selections = e.selections; - this._recomputeMinimapSelections(); + this._minimapSelections = null; return this._actual.onSelectionChanged(); } public onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { @@ -714,8 +779,8 @@ export class Minimap extends ViewPart implements IMinimapModel { return this._actual.onFlushed(); } public onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean { - if (this._isSampling) { - const minimapLineRange = this._modelLineRangeToMinimapLineRange(e.fromLineNumber, e.toLineNumber); + if (this._samplingState) { + const minimapLineRange = this._samplingState.modelLineRangeToMinimapLineRange(e.fromLineNumber, e.toLineNumber); if (minimapLineRange) { return this._actual.onLinesChanged(minimapLineRange[0], minimapLineRange[1]); } else { @@ -726,52 +791,23 @@ export class Minimap extends ViewPart implements IMinimapModel { } } public onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean { - if (this._isSampling) { - // have the mapping be sticky - const deletedLineCount = e.toLineNumber - e.fromLineNumber + 1; - let changeStartIndex = this._minimapLines.length; - let changeEndIndex = 0; - for (let i = this._minimapLines.length - 1; i >= 0; i--) { - if (this._minimapLines[i] < e.fromLineNumber) { - break; - } - if (this._minimapLines[i] <= e.toLineNumber) { - // this line got deleted => move to previous available - this._minimapLines[i] = Math.max(1, e.fromLineNumber - 1); - changeStartIndex = Math.min(changeStartIndex, i); - changeEndIndex = Math.max(changeEndIndex, i); - } else { - this._minimapLines[i] -= deletedLineCount; - } - } + if (this._samplingState) { + const [changeStartIndex, changeEndIndex] = this._samplingState.onLinesDeleted(e); if (changeStartIndex <= changeEndIndex) { this._actual.onLinesChanged(changeStartIndex + 1, changeEndIndex + 1); } this._shouldCheckSampling = true; return true; } else { - if (this.options.entireDocument && this._onOptionsMaybeChanged()) { - return true; - } return this._actual.onLinesDeleted(e.fromLineNumber, e.toLineNumber); } } public onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean { - if (this._isSampling) { - // have the mapping be sticky - const insertedLineCount = e.toLineNumber - e.fromLineNumber + 1; - for (let i = this._minimapLines.length - 1; i >= 0; i--) { - if (this._minimapLines[i] < e.fromLineNumber) { - break; - } - this._minimapLines[i] += insertedLineCount; - } + if (this._samplingState) { + this._samplingState.onLinesInserted(e); this._shouldCheckSampling = true; return true; } else { - if (this.options.entireDocument && this._onOptionsMaybeChanged()) { - return true; - } return this._actual.onLinesInserted(e.fromLineNumber, e.toLineNumber); } } @@ -785,10 +821,10 @@ export class Minimap extends ViewPart implements IMinimapModel { return true; } public onTokensChanged(e: viewEvents.ViewTokensChangedEvent): boolean { - if (this._isSampling) { + if (this._samplingState) { let ranges: { fromLineNumber: number; toLineNumber: number; }[] = []; for (const range of e.ranges) { - const minimapLineRange = this._modelLineRangeToMinimapLineRange(range.fromLineNumber, range.toLineNumber); + const minimapLineRange = this._samplingState.modelLineRangeToMinimapLineRange(range.fromLineNumber, range.toLineNumber); if (minimapLineRange) { ranges.push({ fromLineNumber: minimapLineRange[0], toLineNumber: minimapLineRange[1] }); } @@ -822,9 +858,9 @@ export class Minimap extends ViewPart implements IMinimapModel { let viewportStartLineNumber = ctx.visibleRange.startLineNumber; let viewportEndLineNumber = ctx.visibleRange.endLineNumber; - if (this._isSampling) { - viewportStartLineNumber = this._modelLineToMinimapLine(viewportStartLineNumber); - viewportEndLineNumber = this._modelLineToMinimapLine(viewportEndLineNumber); + if (this._samplingState) { + viewportStartLineNumber = this._samplingState.modelLineToMinimapLine(viewportStartLineNumber); + viewportEndLineNumber = this._samplingState.modelLineToMinimapLine(viewportEndLineNumber); } const minimapCtx: IMinimapRenderingContext = { @@ -847,105 +883,34 @@ export class Minimap extends ViewPart implements IMinimapModel { //#region IMinimapModel - private _recreateLineSampling(): boolean { - const wasSampling = this._isSampling; - const [samplingState, events] = MinimapSamplingState.compute(this._context.configuration, this._context.model, this._minimapLines); - this._isSampling = samplingState.isSampling; - this._samplingRatio = samplingState.samplingRatio; - this._minimapLines = samplingState.minimapLines; + private _recreateLineSampling(): void { + this._minimapSelections = null; - let optionsMightHaveChanged: boolean; - if (wasSampling) { - if (this._isSampling) { - // was sampling, is sampling - for (const event of events) { - switch (event.type) { - case 'deleted': - this._actual.onLinesDeleted(event.deleteFromLineNumber, event.deleteToLineNumber); - break; - case 'inserted': - this._actual.onLinesInserted(event.insertFromLineNumber, event.insertToLineNumber); - break; - case 'flush': - this._actual.onFlushed(); - break; - } + const wasSampling = Boolean(this._samplingState); + const [samplingState, events] = MinimapSamplingState.compute(this._context.configuration.options, this._context.model.getLineCount(), this._samplingState); + this._samplingState = samplingState; + + if (wasSampling && this._samplingState) { + // was sampling, is sampling + for (const event of events) { + switch (event.type) { + case 'deleted': + this._actual.onLinesDeleted(event.deleteFromLineNumber, event.deleteToLineNumber); + break; + case 'inserted': + this._actual.onLinesInserted(event.insertFromLineNumber, event.insertToLineNumber); + break; + case 'flush': + this._actual.onFlushed(); + break; } - optionsMightHaveChanged = false; - } else { - // was not sampling, is sampling - optionsMightHaveChanged = true; - } - } else { - if (this._isSampling) { - // was not sampling, is sampling - optionsMightHaveChanged = true; - } else { - // was not sampling, is not sampling - optionsMightHaveChanged = false; - } - } - - if (optionsMightHaveChanged) { - return this._onOptionsMaybeChanged(); - } - return false; - } - - private _modelLineToMinimapLine(lineNumber: number): number { - return Math.min(this._minimapLines.length, Math.max(1, Math.round(lineNumber / this._samplingRatio))); - } - - /** - * Will return null if the model line ranges are not intersecting with a sampled model line. - */ - private _modelLineRangeToMinimapLineRange(fromLineNumber: number, toLineNumber: number): [number, number] | null { - let fromLineIndex = this._modelLineToMinimapLine(fromLineNumber) - 1; - while (fromLineIndex > 0 && this._minimapLines[fromLineIndex - 1] >= fromLineNumber) { - fromLineIndex--; - } - let toLineIndex = this._modelLineToMinimapLine(toLineNumber) - 1; - while (toLineIndex + 1 < this._minimapLines.length && this._minimapLines[toLineIndex + 1] <= toLineNumber) { - toLineIndex++; - } - if (fromLineIndex === toLineIndex) { - const sampledLineNumber = this._minimapLines[fromLineIndex]; - if (sampledLineNumber < fromLineNumber || sampledLineNumber > toLineNumber) { - // This line is not part of the sampled lines ==> nothing to do - return null; - } - } - return [fromLineIndex + 1, toLineIndex + 1]; - } - - private _recomputeMinimapSelections(): void { - this._minimapSelections = []; - for (const selection of this._selections) { - if (this._isSampling) { - const isMultiline = (selection.startLineNumber !== selection.endLineNumber); - - let minimapLineStart = this._modelLineToMinimapLine(selection.startLineNumber); - let minimapLineEnd = this._modelLineToMinimapLine(selection.endLineNumber); - if (isMultiline && minimapLineEnd === minimapLineStart) { - if (minimapLineEnd === this._minimapLines.length) { - if (minimapLineStart > 1) { - minimapLineStart--; - } - } else { - minimapLineEnd++; - } - } - - this._minimapSelections.push(new Selection(minimapLineStart, selection.startColumn, minimapLineEnd, selection.endColumn)); - } else { - this._minimapSelections.push(selection); } } } public getLineCount(): number { - if (this._isSampling) { - return this._minimapLines.length; + if (this._samplingState) { + return this._samplingState.minimapLines.length; } return this._context.model.getLineCount(); } @@ -955,18 +920,18 @@ export class Minimap extends ViewPart implements IMinimapModel { } public getLineContent(lineNumber: number): string { - if (this._isSampling) { - return this._context.model.getLineContent(this._minimapLines[lineNumber - 1]); + if (this._samplingState) { + return this._context.model.getLineContent(this._samplingState.minimapLines[lineNumber - 1]); } return this._context.model.getLineContent(lineNumber); } public getMinimapLinesRenderingData(startLineNumber: number, endLineNumber: number, needed: boolean[]): (ViewLineData | null)[] { - if (this._isSampling) { + if (this._samplingState) { let result: (ViewLineData | null)[] = []; for (let lineIndex = 0, lineCount = endLineNumber - startLineNumber + 1; lineIndex < lineCount; lineIndex++) { if (needed[lineIndex]) { - result[lineIndex] = this._context.model.getViewLineData(this._minimapLines[startLineNumber + lineIndex - 1]); + result[lineIndex] = this._context.model.getViewLineData(this._samplingState.minimapLines[startLineNumber + lineIndex - 1]); } else { result[lineIndex] = null; } @@ -977,29 +942,40 @@ export class Minimap extends ViewPart implements IMinimapModel { } public getSelections(): Selection[] { + if (this._minimapSelections === null) { + if (this._samplingState) { + this._minimapSelections = []; + for (const selection of this._selections) { + const [minimapLineStart, minimapLineEnd] = this._samplingState.decorationLineRangeToMinimapLineRange(selection.startLineNumber, selection.endLineNumber); + this._minimapSelections.push(new Selection(minimapLineStart, selection.startColumn, minimapLineEnd, selection.endColumn)); + } + } else { + this._minimapSelections = this._selections; + } + } return this._minimapSelections; } public getMinimapDecorationsInViewport(startLineNumber: number, endLineNumber: number): ViewModelDecoration[] { let visibleRange: Range; - if (this._isSampling) { - const modelStartLineNumber = this._minimapLines[startLineNumber - 1]; - const modelEndLineNumber = this._minimapLines[endLineNumber - 1]; + if (this._samplingState) { + const modelStartLineNumber = this._samplingState.minimapLines[startLineNumber - 1]; + const modelEndLineNumber = this._samplingState.minimapLines[endLineNumber - 1]; visibleRange = new Range(modelStartLineNumber, 1, modelEndLineNumber, this._context.model.getLineMaxColumn(modelEndLineNumber)); } else { visibleRange = new Range(startLineNumber, 1, endLineNumber, this._context.model.getLineMaxColumn(endLineNumber)); } const decorations = this._context.model.getDecorationsInViewport(visibleRange); - if (this._isSampling) { + if (this._samplingState) { let result: ViewModelDecoration[] = []; for (const decoration of decorations) { if (!decoration.options.minimap) { continue; } const range = decoration.range; - const minimapStartLineNumber = this._modelLineToMinimapLine(range.startLineNumber); - const minimapEndLineNumber = this._modelLineToMinimapLine(range.endLineNumber); + const minimapStartLineNumber = this._samplingState.modelLineToMinimapLine(range.startLineNumber); + const minimapEndLineNumber = this._samplingState.modelLineToMinimapLine(range.endLineNumber); result.push(new ViewModelDecoration(new Range(minimapStartLineNumber, range.startColumn, minimapEndLineNumber, range.endColumn), decoration.options)); } return result; @@ -1012,8 +988,8 @@ export class Minimap extends ViewPart implements IMinimapModel { } public revealLineNumber(lineNumber: number): void { - if (this._isSampling) { - lineNumber = this._minimapLines[lineNumber - 1]; + if (this._samplingState) { + lineNumber = this._samplingState.minimapLines[lineNumber - 1]; } this._context.privateViewEventBus.emit(new viewEvents.ViewRevealRangeRequestEvent( 'mouse', diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index b312fd966fdb..0a9ae7435d6b 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -8,7 +8,6 @@ import * as platform from 'vs/base/common/platform'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { FontInfo } from 'vs/editor/common/config/fontInfo'; import { Constants } from 'vs/base/common/uint'; -// import { Constants as MinimapConstants } from 'vs/editor/browser/viewParts/minimap/minimapCharSheet'; import { USUAL_WORD_SEPARATORS } from 'vs/editor/common/model/wordHelper'; import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; import { IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; @@ -1759,6 +1758,19 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption 1) { From 51cbc75279d93ab169c69bfffe68530c7cb77779 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 14 Feb 2020 07:42:46 -0800 Subject: [PATCH 055/109] debug: update js-debug-nightly to "2020.2.1117" @ 2020-02-14T01:05:56.75Z --- build/builtInExtensions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/builtInExtensions.json b/build/builtInExtensions.json index c2755a8478bc..eeacf8c78bcd 100644 --- a/build/builtInExtensions.json +++ b/build/builtInExtensions.json @@ -46,7 +46,7 @@ }, { "name": "ms-vscode.js-debug-nightly", - "version": "2020.2.1117", + "version": "2020.2.1317", "forQualities": [ "insider" ], From 8f286708860325a176d1a7d0eb80e0ca55cacb56 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Fri, 14 Feb 2020 07:42:58 -0800 Subject: [PATCH 056/109] remove default view positions from cache --- .../views/browser/viewDescriptorService.ts | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/vs/workbench/services/views/browser/viewDescriptorService.ts b/src/vs/workbench/services/views/browser/viewDescriptorService.ts index 14366966e16f..230a76fe556d 100644 --- a/src/vs/workbench/services/views/browser/viewDescriptorService.ts +++ b/src/vs/workbench/services/views/browser/viewDescriptorService.ts @@ -537,6 +537,22 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } } + // If a value is not present in the cache, it must be reset to default + this.viewContainersRegistry.all.forEach(viewContainer => { + const viewDescriptorCollection = this.getViewDescriptors(viewContainer); + viewDescriptorCollection.allViewDescriptors.forEach(viewDescriptor => { + if (!newCachedPositions.has(viewDescriptor.id)) { + const currentContainer = this.getViewContainer(viewDescriptor.id); + const defaultContainer = this.getDefaultContainer(viewDescriptor.id); + if (currentContainer && defaultContainer && currentContainer !== defaultContainer) { + this.moveViews([viewDescriptor], currentContainer, defaultContainer); + } + + this.cachedViewInfo.delete(viewDescriptor.id); + } + }); + }); + this.cachedViewInfo = this.getCachedViewPositions(); } } @@ -571,6 +587,16 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor }); }); + // Do no save default positions to the cache + // so that default changes can be recognized + // https://github.com/microsoft/vscode/issues/90414 + for (const [viewId, containerInfo] of this.cachedViewInfo) { + const defaultContainer = this.getDefaultContainer(viewId); + if (defaultContainer?.id === containerInfo.containerId) { + this.cachedViewInfo.delete(viewId); + } + } + this.cachedViewPositionsValue = JSON.stringify([...this.cachedViewInfo]); } From a42a6c29776da12701e33eaa09f99546aa69d20d Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 14 Feb 2020 16:48:27 +0100 Subject: [PATCH 057/109] Fix tests --- .../viewLayout/editorLayoutProvider.test.ts | 105 +++++++++++++++--- 1 file changed, 90 insertions(+), 15 deletions(-) diff --git a/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts b/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts index 24a6677c3b35..5f70e4e8d409 100644 --- a/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts +++ b/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts @@ -127,8 +127,13 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.None, minimapLeft: 0, minimapWidth: 0, + minimapIsSampling: false, + minimapScale: 1, minimapLineHeight: 1, - minimapWidthMultiplier: 1, + minimapCanvasInnerWidth: 0, + minimapCanvasInnerHeight: 800, + minimapCanvasOuterWidth: 0, + minimapCanvasOuterHeight: 800, viewportColumn: 98, verticalScrollbarWidth: 0, @@ -183,8 +188,13 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.None, minimapLeft: 0, minimapWidth: 0, + minimapIsSampling: false, + minimapScale: 1, minimapLineHeight: 1, - minimapWidthMultiplier: 1, + minimapCanvasInnerWidth: 0, + minimapCanvasInnerHeight: 800, + minimapCanvasOuterWidth: 0, + minimapCanvasOuterHeight: 800, viewportColumn: 97, verticalScrollbarWidth: 11, @@ -239,8 +249,13 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.None, minimapLeft: 0, minimapWidth: 0, + minimapIsSampling: false, + minimapScale: 1, minimapLineHeight: 1, - minimapWidthMultiplier: 1, + minimapCanvasInnerWidth: 0, + minimapCanvasInnerHeight: 800, + minimapCanvasOuterWidth: 0, + minimapCanvasOuterHeight: 800, viewportColumn: 88, verticalScrollbarWidth: 0, @@ -295,8 +310,13 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.None, minimapLeft: 0, minimapWidth: 0, + minimapIsSampling: false, + minimapScale: 1, minimapLineHeight: 1, - minimapWidthMultiplier: 1, + minimapCanvasInnerWidth: 0, + minimapCanvasInnerHeight: 900, + minimapCanvasOuterWidth: 0, + minimapCanvasOuterHeight: 900, viewportColumn: 88, verticalScrollbarWidth: 0, @@ -351,8 +371,13 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.None, minimapLeft: 0, minimapWidth: 0, + minimapIsSampling: false, + minimapScale: 1, minimapLineHeight: 1, - minimapWidthMultiplier: 1, + minimapCanvasInnerWidth: 0, + minimapCanvasInnerHeight: 900, + minimapCanvasOuterWidth: 0, + minimapCanvasOuterHeight: 900, viewportColumn: 88, verticalScrollbarWidth: 0, @@ -407,8 +432,13 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.None, minimapLeft: 0, minimapWidth: 0, + minimapIsSampling: false, + minimapScale: 1, minimapLineHeight: 1, - minimapWidthMultiplier: 1, + minimapCanvasInnerWidth: 0, + minimapCanvasInnerHeight: 900, + minimapCanvasOuterWidth: 0, + minimapCanvasOuterHeight: 900, viewportColumn: 83, verticalScrollbarWidth: 0, @@ -463,8 +493,13 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.None, minimapLeft: 0, minimapWidth: 0, + minimapIsSampling: false, + minimapScale: 1, minimapLineHeight: 1, - minimapWidthMultiplier: 1, + minimapCanvasInnerWidth: 0, + minimapCanvasInnerHeight: 900, + minimapCanvasOuterWidth: 0, + minimapCanvasOuterHeight: 900, viewportColumn: 83, verticalScrollbarWidth: 0, @@ -519,8 +554,13 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.None, minimapLeft: 0, minimapWidth: 0, + minimapIsSampling: false, + minimapScale: 1, minimapLineHeight: 1, - minimapWidthMultiplier: 1, + minimapCanvasInnerWidth: 0, + minimapCanvasInnerHeight: 900, + minimapCanvasOuterWidth: 0, + minimapCanvasOuterHeight: 900, viewportColumn: 82, verticalScrollbarWidth: 0, @@ -575,8 +615,13 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.None, minimapLeft: 0, minimapWidth: 0, + minimapIsSampling: false, + minimapScale: 1, minimapLineHeight: 1, - minimapWidthMultiplier: 1, + minimapCanvasInnerWidth: 0, + minimapCanvasInnerHeight: 900, + minimapCanvasOuterWidth: 0, + minimapCanvasOuterHeight: 900, viewportColumn: 171, verticalScrollbarWidth: 0, @@ -631,8 +676,13 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.None, minimapLeft: 0, minimapWidth: 0, + minimapIsSampling: false, + minimapScale: 1, minimapLineHeight: 1, - minimapWidthMultiplier: 1, + minimapCanvasInnerWidth: 0, + minimapCanvasInnerHeight: 900, + minimapCanvasOuterWidth: 0, + minimapCanvasOuterHeight: 900, viewportColumn: 169, verticalScrollbarWidth: 0, @@ -687,8 +737,13 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.Text, minimapLeft: 903, minimapWidth: 97, + minimapIsSampling: false, + minimapScale: 1, minimapLineHeight: 2, - minimapWidthMultiplier: 1, + minimapCanvasInnerWidth: 97, + minimapCanvasInnerHeight: 800, + minimapCanvasOuterWidth: 97, + minimapCanvasOuterHeight: 800, viewportColumn: 89, verticalScrollbarWidth: 0, @@ -743,8 +798,13 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.Text, minimapLeft: 903, minimapWidth: 97, + minimapIsSampling: false, + minimapScale: 2, minimapLineHeight: 4, - minimapWidthMultiplier: 1, + minimapCanvasInnerWidth: 194, + minimapCanvasInnerHeight: 1600, + minimapCanvasOuterWidth: 97, + minimapCanvasOuterHeight: 800, viewportColumn: 89, verticalScrollbarWidth: 0, @@ -799,8 +859,13 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.Text, minimapLeft: 945, minimapWidth: 55, + minimapIsSampling: false, + minimapScale: 2, minimapLineHeight: 4, - minimapWidthMultiplier: 1, + minimapCanvasInnerWidth: 220, + minimapCanvasInnerHeight: 3200, + minimapCanvasOuterWidth: 55, + minimapCanvasOuterHeight: 800, viewportColumn: 93, verticalScrollbarWidth: 0, @@ -855,8 +920,13 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.Text, minimapLeft: 0, minimapWidth: 55, + minimapIsSampling: false, + minimapScale: 2, minimapLineHeight: 4, - minimapWidthMultiplier: 1, + minimapCanvasInnerWidth: 220, + minimapCanvasInnerHeight: 3200, + minimapCanvasOuterWidth: 55, + minimapCanvasOuterHeight: 800, viewportColumn: 93, verticalScrollbarWidth: 0, @@ -911,8 +981,13 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.Text, minimapLeft: 1096, minimapWidth: 91, + minimapIsSampling: false, + minimapScale: 2, minimapLineHeight: 4, - minimapWidthMultiplier: 1, + minimapCanvasInnerWidth: 182, + minimapCanvasInnerHeight: 844, + minimapCanvasOuterWidth: 91, + minimapCanvasOuterHeight: 422, viewportColumn: 83, verticalScrollbarWidth: 14, From b51e12159525ded4b690e34338014ab37efe0b3d Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Fri, 14 Feb 2020 07:55:57 -0800 Subject: [PATCH 058/109] panel width --- src/vs/workbench/browser/parts/panel/panelPart.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index b1cdfc6f4eb7..f9d079be854b 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -56,7 +56,7 @@ export class PanelPart extends CompositePart implements IPanelService { //#region IView - readonly minimumWidth: number = 420; + readonly minimumWidth: number = 300; readonly maximumWidth: number = Number.POSITIVE_INFINITY; readonly minimumHeight: number = 77; readonly maximumHeight: number = Number.POSITIVE_INFINITY; From a48bce6f28e814c3c8a5917c59b8deda0069c9a9 Mon Sep 17 00:00:00 2001 From: Bailey Date: Fri, 14 Feb 2020 08:59:30 -0700 Subject: [PATCH 059/109] Revert "Add explorerView check to see if element is visible" This reverts commit 615c24ca3a921879ac4f72d3eb60d8d86ae55102. --- src/vs/base/browser/ui/list/listWidget.ts | 10 ---------- src/vs/base/browser/ui/tree/abstractTree.ts | 13 ------------- src/vs/base/browser/ui/tree/asyncDataTree.ts | 4 ---- .../contrib/files/browser/views/explorerView.ts | 6 +----- 4 files changed, 1 insertion(+), 32 deletions(-) diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 339cc5af913d..026344c5e1b6 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -1546,16 +1546,6 @@ export class List implements ISpliceable, IDisposable { return this.getFocus().map(i => this.view.element(i)); } - isElementVisible(index: number) { - const viewTop = this.view.getScrollTop(); - const viewBottom = this.view.renderHeight + viewTop; - - const elementTop = this.view.elementTop(index); - const elementBottom = this.view.elementHeight(index) + elementTop; - - return (elementTop >= viewTop && elementBottom <= viewBottom); - } - reveal(index: number, relativeTop?: number): void { if (index < 0 || index >= this.length) { throw new ListError(this.user, `Invalid index ${index}`); diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 1032a5a723f7..9e584efcc560 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -1589,19 +1589,6 @@ export abstract class AbstractTree implements IDisposable this.view.open(indexes, browserEvent); } - isElementVisible(location: TRef) { - const index = this.model.getListIndex(location); - - if (index === -1) { - return false; - } - - const parentNode = this.model.getNode(this.model.getParentNodeLocation(location)); - const parentCollapsed = !parentNode.visible || parentNode.collapsed; - - return this.view.isElementVisible(index) && !parentCollapsed; - } - reveal(location: TRef, relativeTop?: number): void { this.model.expandTo(location); diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index 7edd15a2f62b..bbdc7bc60540 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -662,10 +662,6 @@ export class AsyncDataTree implements IDisposable this.tree.open(nodes, browserEvent); } - isElementVisible(element: T) { - return this.tree.isElementVisible(this.getDataNode(element)); - } - reveal(element: T, relativeTop?: number): void { this.tree.reveal(this.getDataNode(element), relativeTop); } diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index cce6ba126a0a..f75377476493 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -675,11 +675,7 @@ export class ExplorerView extends ViewPane { if (item.isDisposed) { return this.onSelectResource(resource, reveal, retry + 1); } - - // Don't scroll to the item if it's already visible - if (!this.tree.isElementVisible(item)) { - this.tree.reveal(item, 0.5); - } + this.tree.reveal(item, 0.5); } this.tree.setFocus([item]); From 631bbceb65911abb6474fa322f83cc6f31fc5e71 Mon Sep 17 00:00:00 2001 From: Bailey Date: Fri, 14 Feb 2020 09:00:43 -0700 Subject: [PATCH 060/109] Switch to getRelativeTop --- .../workbench/contrib/files/browser/views/explorerView.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index f75377476493..c913755c7b4d 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -675,7 +675,11 @@ export class ExplorerView extends ViewPane { if (item.isDisposed) { return this.onSelectResource(resource, reveal, retry + 1); } - this.tree.reveal(item, 0.5); + + // Don't scroll to the item if it's already visible + if (this.tree.getRelativeTop(item) === null) { + this.tree.reveal(item, 0.5); + } } this.tree.setFocus([item]); From 2afb93e0083e7606895969292a0ff8034bdbc993 Mon Sep 17 00:00:00 2001 From: isidor Date: Fri, 14 Feb 2020 17:24:02 +0100 Subject: [PATCH 061/109] debug hover fix margins fixes #90341 --- src/vs/workbench/contrib/debug/browser/debugHover.ts | 5 ++++- src/vs/workbench/contrib/debug/browser/media/debugHover.css | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugHover.ts b/src/vs/workbench/contrib/debug/browser/debugHover.ts index bb510b66fd70..292c33392fce 100644 --- a/src/vs/workbench/contrib/debug/browser/debugHover.ts +++ b/src/vs/workbench/contrib/debug/browser/debugHover.ts @@ -109,8 +109,10 @@ export class DebugHoverWidget implements IContentWidget { accessibilityProvider: new DebugHoverAccessibilityProvider(), mouseSupport: false, horizontalScrolling: true, + useShadows: false, overrideStyles: { - listBackground: editorHoverBackground + listBackground: editorHoverBackground, + } }); @@ -307,6 +309,7 @@ export class DebugHoverWidget implements IContentWidget { } hide(): void { + return; if (!this._isVisible) { return; } diff --git a/src/vs/workbench/contrib/debug/browser/media/debugHover.css b/src/vs/workbench/contrib/debug/browser/media/debugHover.css index b01a3d1193d1..a0406499aa3c 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugHover.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugHover.css @@ -12,7 +12,6 @@ user-select: text; -webkit-user-select: text; word-break: break-all; - padding: 4px 5px; } .monaco-editor .debug-hover-widget .complex-value { @@ -62,6 +61,7 @@ overflow: auto; font-family: var(--monaco-monospace-font); max-height: 500px; + padding: 4px 5px; } .monaco-editor .debug-hover-widget .error { From 7e1c6dcf0fba3c1b066552074e6d2ce4eb2faa7d Mon Sep 17 00:00:00 2001 From: isidor Date: Fri, 14 Feb 2020 17:29:51 +0100 Subject: [PATCH 062/109] fix stupid commit --- src/vs/workbench/contrib/debug/browser/debugHover.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugHover.ts b/src/vs/workbench/contrib/debug/browser/debugHover.ts index 292c33392fce..81638da99127 100644 --- a/src/vs/workbench/contrib/debug/browser/debugHover.ts +++ b/src/vs/workbench/contrib/debug/browser/debugHover.ts @@ -309,7 +309,6 @@ export class DebugHoverWidget implements IContentWidget { } hide(): void { - return; if (!this._isVisible) { return; } From d32013dfd01d790c6bbb9fd5568a2602b58b7c27 Mon Sep 17 00:00:00 2001 From: isidor Date: Fri, 14 Feb 2020 17:31:09 +0100 Subject: [PATCH 063/109] minor lint --- src/vs/workbench/contrib/debug/browser/debugHover.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugHover.ts b/src/vs/workbench/contrib/debug/browser/debugHover.ts index 81638da99127..5de2c78eea24 100644 --- a/src/vs/workbench/contrib/debug/browser/debugHover.ts +++ b/src/vs/workbench/contrib/debug/browser/debugHover.ts @@ -111,8 +111,7 @@ export class DebugHoverWidget implements IContentWidget { horizontalScrolling: true, useShadows: false, overrideStyles: { - listBackground: editorHoverBackground, - + listBackground: editorHoverBackground } }); From 57b0310b21dd2ba4890e2dc7773cfe8a2bbdf6ba Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 14 Feb 2020 17:39:59 +0100 Subject: [PATCH 064/109] Add tests for minimap entireDocument computation --- .../viewLayout/editorLayoutProvider.test.ts | 132 +++++++++++++++++- 1 file changed, 130 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts b/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts index 5f70e4e8d409..a69efcd6a929 100644 --- a/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts +++ b/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts @@ -17,6 +17,7 @@ interface IEditorLayoutProviderOpts { readonly showLineNumbers: boolean; readonly lineNumbersMinChars: number; readonly lineNumbersDigitCount: number; + maxLineNumber?: number; readonly lineDecorationsWidth: number; @@ -32,6 +33,7 @@ interface IEditorLayoutProviderOpts { readonly minimapSide: 'left' | 'right'; readonly minimapRenderCharacters: boolean; readonly minimapMaxColumn: number; + minimapEntireDocument?: boolean; readonly pixelRatio: number; } @@ -50,7 +52,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { maxColumn: input.minimapMaxColumn, showSlider: 'mouseover', scale: 1, - entireDocument: false + entireDocument: input.minimapEntireDocument || false }; options._write(EditorOption.minimap, minimapOptions); const scrollbarOptions: InternalEditorScrollbarOptions = { @@ -78,7 +80,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { outerWidth: input.outerWidth, outerHeight: input.outerHeight, lineHeight: input.lineHeight, - maxLineNumber: Math.pow(10, input.lineNumbersDigitCount) - 1, + maxLineNumber: input.maxLineNumber || Math.pow(10, input.lineNumbersDigitCount) - 1, lineNumbersDigitCount: input.lineNumbersDigitCount, typicalHalfwidthCharacterWidth: input.typicalHalfwidthCharacterWidth, maxDigitWidth: input.maxDigitWidth, @@ -941,6 +943,132 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { }); }); + test('EditorLayoutProvider 11 - render entire document in minimap without sampling', () => { + doTest({ + outerWidth: 1000, + outerHeight: 800, + showGlyphMargin: false, + lineHeight: 16, + showLineNumbers: false, + lineNumbersMinChars: 0, + lineNumbersDigitCount: 3, + maxLineNumber: 120, + lineDecorationsWidth: 10, + typicalHalfwidthCharacterWidth: 10, + maxDigitWidth: 10, + verticalScrollbarWidth: 0, + horizontalScrollbarHeight: 0, + scrollbarArrowSize: 0, + verticalScrollbarHasArrows: false, + minimap: true, + minimapSide: 'right', + minimapRenderCharacters: true, + minimapMaxColumn: 150, + minimapEntireDocument: true, + pixelRatio: 2, + }, { + width: 1000, + height: 800, + + glyphMarginLeft: 0, + glyphMarginWidth: 0, + + lineNumbersLeft: 0, + lineNumbersWidth: 0, + + decorationsLeft: 0, + decorationsWidth: 10, + + contentLeft: 10, + contentWidth: 818, + + renderMinimap: RenderMinimap.Text, + minimapLeft: 828, + minimapWidth: 172, + minimapIsSampling: false, + minimapScale: 4, + minimapLineHeight: 13, + minimapCanvasInnerWidth: 344, + minimapCanvasInnerHeight: 1560, + minimapCanvasOuterWidth: 172, + minimapCanvasOuterHeight: 800, + viewportColumn: 81, + + verticalScrollbarWidth: 0, + horizontalScrollbarHeight: 0, + + overviewRuler: { + top: 0, + width: 0, + height: 800, + right: 0 + } + }); + }); + + test('EditorLayoutProvider 12 - render entire document in minimap with sampling', () => { + doTest({ + outerWidth: 1000, + outerHeight: 800, + showGlyphMargin: false, + lineHeight: 16, + showLineNumbers: false, + lineNumbersMinChars: 0, + lineNumbersDigitCount: 4, + maxLineNumber: 2500, + lineDecorationsWidth: 10, + typicalHalfwidthCharacterWidth: 10, + maxDigitWidth: 10, + verticalScrollbarWidth: 0, + horizontalScrollbarHeight: 0, + scrollbarArrowSize: 0, + verticalScrollbarHasArrows: false, + minimap: true, + minimapSide: 'right', + minimapRenderCharacters: true, + minimapMaxColumn: 150, + minimapEntireDocument: true, + pixelRatio: 2, + }, { + width: 1000, + height: 800, + + glyphMarginLeft: 0, + glyphMarginWidth: 0, + + lineNumbersLeft: 0, + lineNumbersWidth: 0, + + decorationsLeft: 0, + decorationsWidth: 10, + + contentLeft: 10, + contentWidth: 935, + + renderMinimap: RenderMinimap.Text, + minimapLeft: 945, + minimapWidth: 55, + minimapIsSampling: true, + minimapScale: 1, + minimapLineHeight: 1, + minimapCanvasInnerWidth: 110, + minimapCanvasInnerHeight: 1600, + minimapCanvasOuterWidth: 55, + minimapCanvasOuterHeight: 800, + viewportColumn: 93, + + verticalScrollbarWidth: 0, + horizontalScrollbarHeight: 0, + + overviewRuler: { + top: 0, + width: 0, + height: 800, + right: 0 + } + }); + }); + test('issue #31312: When wrapping, leave 2px for the cursor', () => { doTest({ outerWidth: 1201, From f2ca025aa05005d7bcb6d833a545e382961685c5 Mon Sep 17 00:00:00 2001 From: chrisdias Date: Fri, 14 Feb 2020 11:05:49 -0800 Subject: [PATCH 065/109] update distro --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e8d355e18cd7..2712fd40e09b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.43.0", - "distro": "a87981a5cc52fed24735d447e954adccb9c9f914", + "distro": "821d61f0e38d2eb3e02bec3962fe61e4e5a61527", "author": { "name": "Microsoft Corporation" }, From c03b581840ec6627b68041655e7cd0e40dad3a20 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 13 Feb 2020 16:23:26 -0800 Subject: [PATCH 066/109] Add test for copying text from webview --- .../src/singlefolder-tests/webview.test.ts | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts index e785f1d4afbe..df8af33f6b2c 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts @@ -7,7 +7,7 @@ import 'mocha'; import * as assert from 'assert'; import * as vscode from 'vscode'; import { join } from 'path'; -import { closeAllEditors, disposeAll, conditionalTest } from '../utils'; +import { closeAllEditors, disposeAll, conditionalTest, delay } from '../utils'; const webviewId = 'myWebview'; @@ -332,7 +332,27 @@ suite('Webview tests', () => { webview.webview.postMessage({ value: 1 }); await firstResponse; assert.strictEqual(webview.viewColumn, vscode.ViewColumn.One); + }); + test('webview can copy text from webview', async () => { + const expectedText = `webview text from: ${Date.now()}!`; + + const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, { enableScripts: true, retainContextWhenHidden: true })); + const ready = getMesssage(webview); + + + webview.webview.html = createHtmlDocumentWithBody(/*html*/` + ${expectedText} + `); + await ready; + + await vscode.commands.executeCommand('editor.action.webvieweditor.copy'); + await delay(200); // Make sure copy has time to reach webview + assert.strictEqual(await vscode.env.clipboard.readText(), expectedText); }); }); From 20fae66e378e5af6bcf8f51f650ff790f697f58e Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 13 Feb 2020 16:28:45 -0800 Subject: [PATCH 067/109] Add type annotations --- src/vs/workbench/contrib/webview/browser/pre/main.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/webview/browser/pre/main.js b/src/vs/workbench/contrib/webview/browser/pre/main.js index 63c9af47e20c..d82a472b1725 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/main.js +++ b/src/vs/workbench/contrib/webview/browser/pre/main.js @@ -449,6 +449,10 @@ }, 0); }); + /** + * @param {Document} contentDocument + * @param {Window} contentWindow + */ const onLoad = (contentDocument, contentWindow) => { if (contentDocument && contentDocument.body) { // Workaround for https://github.com/Microsoft/vscode/issues/12865 @@ -492,10 +496,12 @@ }, 200); newFrame.contentWindow.addEventListener('load', function (e) { + const contentDocument = /** @type {Document} */ (e.target); + if (loadTimeout) { clearTimeout(loadTimeout); loadTimeout = undefined; - onLoad(e.target, this); + onLoad(contentDocument, this); } }); From 8fd47cd5090f92d5fed25def26d6aa608ef6e3f8 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 13 Feb 2020 16:54:17 -0800 Subject: [PATCH 068/109] Enable select all in iframe based webviews --- .../webview/browser/baseWebviewElement.ts | 6 +++ .../browser/dynamicWebviewEditorOverlay.ts | 1 + .../contrib/webview/browser/pre/main.js | 39 +++++++++++-------- .../webview/browser/webview.contribution.ts | 7 +++- .../contrib/webview/browser/webview.ts | 2 + .../webview/browser/webviewCommands.ts | 24 ++++++++++++ .../contrib/webview/browser/webviewEditor.ts | 6 +++ .../electron-browser/webview.contribution.ts | 2 - .../electron-browser/webviewCommands.ts | 22 ----------- 9 files changed, 68 insertions(+), 41 deletions(-) diff --git a/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts b/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts index 5cf9df18ef8a..d8f0b88ebc3d 100644 --- a/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts @@ -286,4 +286,10 @@ export abstract class BaseWebview extends Disposable { this.element.style.pointerEvents = ''; } } + + public selectAll() { + if (this.element) { + this._send('execCommand', 'selectAll'); + } + } } diff --git a/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts b/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts index 9848ce0cb706..fe444e8070df 100644 --- a/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts +++ b/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts @@ -187,6 +187,7 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewEd showFind(): void { this.withWebview(webview => webview.showFind()); } hideFind(): void { this.withWebview(webview => webview.hideFind()); } runFindAction(previous: boolean): void { this.withWebview(webview => webview.runFindAction(previous)); } + selectAll(): void { this.withWebview(webview => webview.selectAll()); } public getInnerWebview() { return this._webview.value; diff --git a/src/vs/workbench/contrib/webview/browser/pre/main.js b/src/vs/workbench/contrib/webview/browser/pre/main.js index d82a472b1725..aa318da9d65b 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/main.js +++ b/src/vs/workbench/contrib/webview/browser/pre/main.js @@ -232,23 +232,23 @@ * @param {MouseEvent} event */ const handleAuxClick = - (event) => { - // Prevent middle clicks opening a broken link in the browser - if (!event.view || !event.view.document) { - return; - } - - if (event.button === 1) { - let node = /** @type {any} */ (event.target); - while (node) { - if (node.tagName && node.tagName.toLowerCase() === 'a' && node.href) { - event.preventDefault(); - break; - } - node = node.parentNode; + (event) => { + // Prevent middle clicks opening a broken link in the browser + if (!event.view || !event.view.document) { + return; } - } - }; + + if (event.button === 1) { + let node = /** @type {any} */ (event.target); + while (node) { + if (node.tagName && node.tagName.toLowerCase() === 'a' && node.href) { + event.preventDefault(); + break; + } + node = node.parentNode; + } + } + }; /** * @param {KeyboardEvent} e @@ -545,6 +545,13 @@ initData.initialScrollProgress = progress; }); + host.onMessage('execCommand', (_event, data) => { + const target = getActiveFrame(); + if (!target) { + return; + } + target.contentDocument.execCommand(data); + }); trackFocus({ onFocus: () => host.postMessage('did-focus'), diff --git a/src/vs/workbench/contrib/webview/browser/webview.contribution.ts b/src/vs/workbench/contrib/webview/browser/webview.contribution.ts index c1ce99713e4f..b8240e1bc17a 100644 --- a/src/vs/workbench/contrib/webview/browser/webview.contribution.ts +++ b/src/vs/workbench/contrib/webview/browser/webview.contribution.ts @@ -14,7 +14,7 @@ import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/wor import { Extensions as EditorInputExtensions, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; import { webviewDeveloperCategory } from 'vs/workbench/contrib/webview/browser/webview'; import { WebviewEditorInputFactory } from 'vs/workbench/contrib/webview/browser/webviewEditorInputFactory'; -import { HideWebViewEditorFindCommand, ReloadWebviewAction, ShowWebViewEditorFindWidgetAction, WebViewEditorFindNextCommand, WebViewEditorFindPreviousCommand } from '../browser/webviewCommands'; +import { HideWebViewEditorFindCommand, ReloadWebviewAction, ShowWebViewEditorFindWidgetAction, WebViewEditorFindNextCommand, WebViewEditorFindPreviousCommand, SelectAllWebviewEditorCommand } from '../browser/webviewCommands'; import { WebviewEditor } from './webviewEditor'; import { WebviewInput } from './webviewEditorInput'; import { IWebviewWorkbenchService, WebviewEditorService } from './webviewWorkbenchService'; @@ -50,6 +50,11 @@ registerAction2(class extends WebViewEditorFindPreviousCommand { constructor() { super(webviewActiveContextKeyExpr); } }); +registerAction2(class extends SelectAllWebviewEditorCommand { + constructor() { super(webviewActiveContextKeyExpr); } +}); + + const actionRegistry = Registry.as(ActionExtensions.WorkbenchActions); actionRegistry.registerWorkbenchAction( SyncActionDescriptor.create(ReloadWebviewAction, ReloadWebviewAction.ID, ReloadWebviewAction.LABEL), diff --git a/src/vs/workbench/contrib/webview/browser/webview.ts b/src/vs/workbench/contrib/webview/browser/webview.ts index 784bef65db4d..b65f28416f2b 100644 --- a/src/vs/workbench/contrib/webview/browser/webview.ts +++ b/src/vs/workbench/contrib/webview/browser/webview.ts @@ -93,6 +93,8 @@ export interface Webview extends IDisposable { hideFind(): void; runFindAction(previous: boolean): void; + selectAll(): void; + windowDidDragStart(): void; windowDidDragEnd(): void; } diff --git a/src/vs/workbench/contrib/webview/browser/webviewCommands.ts b/src/vs/workbench/contrib/webview/browser/webviewCommands.ts index 9845fb414d91..5f870d1d15e5 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewCommands.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewCommands.ts @@ -13,6 +13,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis import { KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_FOCUSED, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE } from 'vs/workbench/contrib/webview/browser/webview'; import { WebviewEditor } from 'vs/workbench/contrib/webview/browser/webviewEditor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; export class ShowWebViewEditorFindWidgetAction extends Action2 { public static readonly ID = 'editor.action.webvieweditor.showFind'; @@ -97,6 +98,29 @@ export class WebViewEditorFindPreviousCommand extends Action2 { getActiveWebviewEditor(accessor)?.find(true); } } + +export class SelectAllWebviewEditorCommand extends Action2 { + public static readonly ID = 'editor.action.webvieweditor.selectAll'; + public static readonly LABEL = nls.localize('editor.action.webvieweditor.selectAll', 'Select all'); + + constructor(contextKeyExpr: ContextKeyExpr) { + const precondition = ContextKeyExpr.and(contextKeyExpr, ContextKeyExpr.not(InputFocusedContextKey)); + super({ + id: SelectAllWebviewEditorCommand.ID, + title: SelectAllWebviewEditorCommand.LABEL, + keybinding: { + when: precondition, + primary: KeyMod.CtrlCmd | KeyCode.KEY_A, + weight: KeybindingWeight.EditorContrib + } + }); + } + + public run(accessor: ServicesAccessor, args: any): void { + getActiveWebviewEditor(accessor)?.selectAll(); + } +} + export class ReloadWebviewAction extends Action { static readonly ID = 'workbench.action.webview.reloadWebviewAction'; static readonly LABEL = nls.localize('refreshWebviewLabel', "Reload Webviews"); diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditor.ts b/src/vs/workbench/contrib/webview/browser/webviewEditor.ts index a0e741c29170..449d93aebfa1 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditor.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditor.ts @@ -88,6 +88,12 @@ export class WebviewEditor extends BaseEditor { }); } + public selectAll() { + this.withWebview(webview => { + webview.selectAll(); + }); + } + public reload() { this.withWebview(webview => webview.reload()); } diff --git a/src/vs/workbench/contrib/webview/electron-browser/webview.contribution.ts b/src/vs/workbench/contrib/webview/electron-browser/webview.contribution.ts index 538e36abc333..b0471a3d0a13 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webview.contribution.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webview.contribution.ts @@ -26,8 +26,6 @@ actionRegistry.registerWorkbenchAction( function registerWebViewCommands(editorId: string): void { const contextKeyExpr = ContextKeyExpr.and(ContextKeyExpr.equals('activeEditor', editorId), ContextKeyExpr.not('editorFocus') /* https://github.com/Microsoft/vscode/issues/58668 */)!; - registerAction2(class extends webviewCommands.SelectAllWebviewEditorCommand { constructor() { super(contextKeyExpr); } }); - // These commands are only needed on MacOS where we have to disable the menu bar commands if (isMacintosh) { registerAction2(class extends webviewCommands.CopyWebviewEditorCommand { constructor() { super(contextKeyExpr); } }); diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts index 611622ed72b1..b06bef4aafde 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts @@ -38,28 +38,6 @@ export class OpenWebviewDeveloperToolsAction extends Action { } } -export class SelectAllWebviewEditorCommand extends Action2 { - public static readonly ID = 'editor.action.webvieweditor.selectAll'; - public static readonly LABEL = nls.localize('editor.action.webvieweditor.selectAll', 'Select all'); - - constructor(contextKeyExpr: ContextKeyExpr) { - const precondition = ContextKeyExpr.and(contextKeyExpr, ContextKeyExpr.not(InputFocusedContextKey)); - super({ - id: SelectAllWebviewEditorCommand.ID, - title: SelectAllWebviewEditorCommand.LABEL, - keybinding: { - when: precondition, - primary: KeyMod.CtrlCmd | KeyCode.KEY_A, - weight: KeybindingWeight.EditorContrib - } - }); - } - - public run(accessor: ServicesAccessor, args: any): void { - withActiveWebviewBasedWebview(accessor, webview => webview.selectAll()); - } -} - export class CopyWebviewEditorCommand extends Action2 { public static readonly ID = 'editor.action.webvieweditor.copy'; public static readonly LABEL = nls.localize('editor.action.webvieweditor.copy', "Copy2"); From 8b087788744d8c2986602f56fc5d23c074b032b8 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 13 Feb 2020 16:59:39 -0800 Subject: [PATCH 069/109] Replace withWebview with optional chaining usage --- .../contrib/webview/browser/webviewEditor.ts | 55 ++++++++----------- 1 file changed, 22 insertions(+), 33 deletions(-) diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditor.ts b/src/vs/workbench/contrib/webview/browser/webviewEditor.ts index 449d93aebfa1..97eb2c50bd0d 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditor.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditor.ts @@ -15,7 +15,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; import { EditorInput, EditorOptions } from 'vs/workbench/common/editor'; -import { KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE, Webview, WebviewEditorOverlay } from 'vs/workbench/contrib/webview/browser/webview'; +import { KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE, WebviewEditorOverlay } from 'vs/workbench/contrib/webview/browser/webview'; import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -71,37 +71,33 @@ export class WebviewEditor extends BaseEditor { } public showFind() { - this.withWebview(webview => { - webview.showFind(); + if (this.webview) { + this.webview.showFind(); this._findWidgetVisible.set(true); - }); + } } public hideFind() { this._findWidgetVisible.reset(); - this.withWebview(webview => webview.hideFind()); + this.webview?.hideFind(); } public find(previous: boolean) { - this.withWebview(webview => { - webview.runFindAction(previous); - }); + this.webview?.runFindAction(previous); } public selectAll() { - this.withWebview(webview => { - webview.selectAll(); - }); + this.webview?.selectAll(); } public reload() { - this.withWebview(webview => webview.reload()); + this.webview?.reload(); } public layout(dimension: DOM.Dimension): void { this._dimension = dimension; - if (this.input && this.input instanceof WebviewInput) { - this.synchronizeWebviewContainerDimensions(this.input.webview, dimension); + if (this.webview) { + this.synchronizeWebviewContainerDimensions(this.webview, dimension); } } @@ -115,22 +111,19 @@ export class WebviewEditor extends BaseEditor { } }); } - this.withWebview(webview => webview.focus()); + this.webview?.focus(); } - public withWebview(f: (element: Webview) => void): void { - if (this.input && this.input instanceof WebviewInput) { - f(this.input.webview); - } + public get webview(): WebviewEditorOverlay | undefined { + return this.input instanceof WebviewInput ? this.input.webview : undefined; } protected setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { - if (this.input instanceof WebviewInput) { - const webview = this.input.webview; + if (this.input instanceof WebviewInput && this.webview) { if (visible) { - webview.claim(this); + this.webview.claim(this); } else { - webview.release(this); + this.webview.release(this); } this.claimWebview(this.input); } @@ -138,8 +131,8 @@ export class WebviewEditor extends BaseEditor { } public clearInput() { - if (this.input && this.input instanceof WebviewInput) { - this.input.webview.release(this); + if (this.webview) { + this.webview.release(this); this._webviewVisibleDisposables.clear(); } @@ -151,8 +144,8 @@ export class WebviewEditor extends BaseEditor { return; } - if (this.input && this.input instanceof WebviewInput) { - this.input.webview.release(this); + if (this.webview) { + this.webview.release(this); } await super.setInput(input, options, token); @@ -195,15 +188,11 @@ export class WebviewEditor extends BaseEditor { } this._webviewVisibleDisposables.add(DOM.addDisposableListener(window, DOM.EventType.DRAG_START, () => { - if (this.input instanceof WebviewInput) { - this.input.webview.windowDidDragStart(); - } + this.webview?.windowDidDragStart(); })); const onDragEnd = () => { - if (this.input instanceof WebviewInput) { - this.input.webview.windowDidDragEnd(); - } + this.webview?.windowDidDragEnd(); }; this._webviewVisibleDisposables.add(DOM.addDisposableListener(window, DOM.EventType.DRAG_END, onDragEnd)); this._webviewVisibleDisposables.add(DOM.addDisposableListener(window, DOM.EventType.MOUSE_MOVE, currentEvent => { From 31eb2b1d25d5a985f7506d047026de21c1db0e18 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 14 Feb 2020 12:22:20 -0800 Subject: [PATCH 070/109] Also switch getActiveWebviewBasedWebview to use optional chaining --- .../electron-browser/webviewCommands.ts | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts index b06bef4aafde..ebbe40c7f428 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts @@ -55,7 +55,7 @@ export class CopyWebviewEditorCommand extends Action2 { } public run(accessor: ServicesAccessor): void { - withActiveWebviewBasedWebview(accessor, webview => webview.copy()); + getActiveWebviewBasedWebview(accessor)?.copy(); } } @@ -76,7 +76,7 @@ export class PasteWebviewEditorCommand extends Action2 { } public run(accessor: ServicesAccessor): void { - withActiveWebviewBasedWebview(accessor, webview => webview.paste()); + getActiveWebviewBasedWebview(accessor)?.paste(); } } @@ -97,7 +97,7 @@ export class CutWebviewEditorCommand extends Action2 { } public run(accessor: ServicesAccessor): void { - withActiveWebviewBasedWebview(accessor, webview => webview.cut()); + getActiveWebviewBasedWebview(accessor)?.cut(); } } @@ -118,7 +118,7 @@ export class UndoWebviewEditorCommand extends Action2 { } public run(accessor: ServicesAccessor, args: any): void { - withActiveWebviewBasedWebview(accessor, webview => webview.undo()); + getActiveWebviewBasedWebview(accessor)?.undo(); } } @@ -141,22 +141,24 @@ export class RedoWebviewEditorCommand extends Action2 { } public run(accessor: ServicesAccessor, args: any): void { - withActiveWebviewBasedWebview(accessor, webview => webview.redo()); + getActiveWebviewBasedWebview(accessor)?.redo(); } } -function withActiveWebviewBasedWebview(accessor: ServicesAccessor, f: (webview: ElectronWebviewBasedWebview) => void): void { - const webViewEditor = getActiveWebviewEditor(accessor); - if (webViewEditor) { - webViewEditor.withWebview(webview => { - if (webview instanceof ElectronWebviewBasedWebview) { - f(webview); - } else if ((webview as WebviewEditorOverlay).getInnerWebview) { - const innerWebview = (webview as WebviewEditorOverlay).getInnerWebview(); - if (innerWebview instanceof ElectronWebviewBasedWebview) { - f(innerWebview); - } - } - }); +function getActiveWebviewBasedWebview(accessor: ServicesAccessor): ElectronWebviewBasedWebview | undefined { + const webview = getActiveWebviewEditor(accessor)?.webview; + if (!webview) { + return undefined; } + + if (webview instanceof ElectronWebviewBasedWebview) { + return webview; + } else if ((webview as WebviewEditorOverlay).getInnerWebview) { + const innerWebview = (webview as WebviewEditorOverlay).getInnerWebview(); + if (innerWebview instanceof ElectronWebviewBasedWebview) { + return innerWebview; + } + } + + return undefined; } From 69b30f6ba7ff6d6997a533595a2fdbc2cd85f2c0 Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Mon, 10 Feb 2020 14:49:59 -0500 Subject: [PATCH 071/109] Adds paging support (wip) --- extensions/git/src/timelineProvider.ts | 8 +-- src/vs/vscode.proposed.d.ts | 39 ++++++++++++- .../api/browser/mainThreadTimeline.ts | 10 +--- .../workbench/api/common/extHost.protocol.ts | 6 +- .../workbench/api/common/extHostTimeline.ts | 24 +++++--- .../contrib/timeline/browser/timelinePane.ts | 4 +- .../contrib/timeline/common/timeline.ts | 22 ++++++-- .../timeline/common/timelineService.ts | 55 ++++--------------- 8 files changed, 92 insertions(+), 76 deletions(-) diff --git a/extensions/git/src/timelineProvider.ts b/extensions/git/src/timelineProvider.ts index 3aa36a0dccbe..a83436dc29eb 100644 --- a/extensions/git/src/timelineProvider.ts +++ b/extensions/git/src/timelineProvider.ts @@ -6,7 +6,7 @@ import * as dayjs from 'dayjs'; import * as advancedFormat from 'dayjs/plugin/advancedFormat'; import * as relativeTime from 'dayjs/plugin/relativeTime'; -import { CancellationToken, Disposable, Event, EventEmitter, ThemeIcon, TimelineItem, TimelineProvider, Uri, workspace, TimelineChangeEvent } from 'vscode'; +import { CancellationToken, Disposable, Event, EventEmitter, ThemeIcon, Timeline, TimelineChangeEvent, TimelineCursor, TimelineItem, TimelineProvider, Uri, workspace } from 'vscode'; import { Model } from './model'; import { Repository } from './repository'; import { debounce } from './decorators'; @@ -44,7 +44,7 @@ export class GitTimelineProvider implements TimelineProvider { this._disposable.dispose(); } - async provideTimeline(uri: Uri, _token: CancellationToken): Promise { + async provideTimeline(uri: Uri, _cursor: TimelineCursor, _token: CancellationToken): Promise { // console.log(`GitTimelineProvider.provideTimeline: uri=${uri} state=${this._model.state}`); const repo = this._model.getRepository(uri); @@ -53,7 +53,7 @@ export class GitTimelineProvider implements TimelineProvider { this._repoStatusDate = undefined; this._repo = undefined; - return []; + return { items: [] }; } if (this._repo?.root !== repo.root) { @@ -181,7 +181,7 @@ export class GitTimelineProvider implements TimelineProvider { items.push(item); } - return items; + return { items: items }; } private onRepositoriesChanged(_repo: Repository) { diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index deb1bae00507..637dff308173 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1611,6 +1611,40 @@ declare module 'vscode' { uri?: Uri; } + export interface TimelineCursor { + /** + * A provider-defined cursor specifing the range of timeline items to be returned. Must be serializable. + */ + cursor?: any; + + /** + * A flag to specify whether the timeline items requested are before or after (default) the provided cursor. + */ + before?: boolean; + + /** + * The maximum number of timeline items that should be returned. + */ + limit?: number; + } + + export interface Timeline { + /** + * A provider-defined cursor specifing the range of timeline items returned. Must be serializable. + */ + cursor?: any; + + /** + * A flag which indicates whether there are any more items that weren't returned. + */ + more?: boolean; + + /** + * An array of [timeline items](#TimelineItem). + */ + items: TimelineItem[]; + } + export interface TimelineProvider { /** * An optional event to signal that the timeline for a source has changed. @@ -1633,10 +1667,11 @@ declare module 'vscode' { * * @param uri The [uri](#Uri) of the file to provide the timeline for. * @param token A cancellation token. - * @return An array of timeline items or a thenable that resolves to such. The lack of a result + * @param cursor TBD + * @return The [timeline result](#TimelineResult) or a thenable that resolves to such. The lack of a result * can be signaled by returning `undefined`, `null`, or an empty array. */ - provideTimeline(uri: Uri, token: CancellationToken): ProviderResult; + provideTimeline(uri: Uri, cursor: TimelineCursor, token: CancellationToken): ProviderResult; } export namespace workspace { diff --git a/src/vs/workbench/api/browser/mainThreadTimeline.ts b/src/vs/workbench/api/browser/mainThreadTimeline.ts index 919e4a18ff5b..f65a89b432c9 100644 --- a/src/vs/workbench/api/browser/mainThreadTimeline.ts +++ b/src/vs/workbench/api/browser/mainThreadTimeline.ts @@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri'; import { ILogService } from 'vs/platform/log/common/log'; import { MainContext, MainThreadTimelineShape, IExtHostContext, ExtHostTimelineShape, ExtHostContext } from 'vs/workbench/api/common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; -import { ITimelineService, TimelineItem, TimelineProviderDescriptor, TimelineChangeEvent } from 'vs/workbench/contrib/timeline/common/timeline'; +import { TimelineChangeEvent, TimelineCursor, TimelineProviderDescriptor, ITimelineService } from 'vs/workbench/contrib/timeline/common/timeline'; @extHostNamedCustomer(MainContext.MainThreadTimeline) export class MainThreadTimeline implements MainThreadTimelineShape { @@ -24,10 +24,6 @@ export class MainThreadTimeline implements MainThreadTimelineShape { this._proxy = context.getProxy(ExtHostContext.ExtHostTimeline); } - $getTimeline(uri: URI, token: CancellationToken): Promise { - return this._timelineService.getTimeline(uri, token); - } - $registerTimelineProvider(provider: TimelineProviderDescriptor): void { this.logService.trace(`MainThreadTimeline#registerTimelineProvider: id=${provider.id}`); @@ -43,8 +39,8 @@ export class MainThreadTimeline implements MainThreadTimelineShape { this._timelineService.registerTimelineProvider({ ...provider, onDidChange: onDidChange.event, - provideTimeline(uri: URI, token: CancellationToken) { - return proxy.$getTimeline(provider.id, uri, token); + provideTimeline(uri: URI, cursor: TimelineCursor, token: CancellationToken) { + return proxy.$getTimeline(provider.id, uri, cursor, token); }, dispose() { emitters.delete(provider.id); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 73af521adefb..21d4884818ee 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -49,7 +49,7 @@ import { SaveReason } from 'vs/workbench/common/editor'; import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator'; import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; import { TunnelOptions } from 'vs/platform/remote/common/tunnel'; -import { TimelineItem, TimelineProviderDescriptor, TimelineChangeEvent, TimelineItemWithSource } from 'vs/workbench/contrib/timeline/common/timeline'; +import { Timeline, TimelineChangeEvent, TimelineCursor, TimelineProviderDescriptor } from 'vs/workbench/contrib/timeline/common/timeline'; export interface IEnvironment { isExtensionDevelopmentDebug: boolean; @@ -803,8 +803,6 @@ export interface MainThreadTimelineShape extends IDisposable { $registerTimelineProvider(provider: TimelineProviderDescriptor): void; $unregisterTimelineProvider(source: string): void; $emitTimelineChangeEvent(e: TimelineChangeEvent): void; - - $getTimeline(uri: UriComponents, token: CancellationToken): Promise; } // -- extension host @@ -1459,7 +1457,7 @@ export interface ExtHostTunnelServiceShape { } export interface ExtHostTimelineShape { - $getTimeline(source: string, uri: UriComponents, token: CancellationToken): Promise; + $getTimeline(source: string, uri: UriComponents, cursor: TimelineCursor, token: CancellationToken): Promise; } // --- proxy identifiers diff --git a/src/vs/workbench/api/common/extHostTimeline.ts b/src/vs/workbench/api/common/extHostTimeline.ts index 1ce8f7813ffd..c4e3832649a6 100644 --- a/src/vs/workbench/api/common/extHostTimeline.ts +++ b/src/vs/workbench/api/common/extHostTimeline.ts @@ -7,7 +7,7 @@ import * as vscode from 'vscode'; import { UriComponents, URI } from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ExtHostTimelineShape, MainThreadTimelineShape, IMainContext, MainContext } from 'vs/workbench/api/common/extHost.protocol'; -import { TimelineItemWithSource, TimelineProvider } from 'vs/workbench/contrib/timeline/common/timeline'; +import { Timeline, TimelineCursor, TimelineItemWithSource, TimelineProvider } from 'vs/workbench/contrib/timeline/common/timeline'; import { IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { CancellationToken } from 'vs/base/common/cancellation'; import { CommandsConverter } from 'vs/workbench/api/common/extHostCommands'; @@ -16,7 +16,7 @@ import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; export interface IExtHostTimeline extends ExtHostTimelineShape { readonly _serviceBrand: undefined; - $getTimeline(id: string, uri: UriComponents, token: vscode.CancellationToken): Promise; + $getTimeline(id: string, uri: UriComponents, cursor: vscode.TimelineCursor, token: vscode.CancellationToken): Promise; } export const IExtHostTimeline = createDecorator('IExtHostTimeline'); @@ -34,9 +34,9 @@ export class ExtHostTimeline implements IExtHostTimeline { this._proxy = mainContext.getProxy(MainContext.MainThreadTimeline); } - async $getTimeline(id: string, uri: UriComponents, token: vscode.CancellationToken): Promise { + async $getTimeline(id: string, uri: UriComponents, cursor: vscode.TimelineCursor, token: vscode.CancellationToken): Promise { const provider = this._providers.get(id); - return provider?.provideTimeline(URI.revive(uri), token) ?? []; + return provider?.provideTimeline(URI.revive(uri), cursor, token); } registerTimelineProvider(scheme: string | string[], provider: vscode.TimelineProvider, extensionId: ExtensionIdentifier, commandConverter: CommandsConverter): IDisposable { @@ -53,15 +53,21 @@ export class ExtHostTimeline implements IExtHostTimeline { ...provider, scheme: scheme, onDidChange: undefined, - async provideTimeline(uri: URI, token: CancellationToken) { + async provideTimeline(uri: URI, cursor: TimelineCursor, token: CancellationToken) { timelineDisposables.clear(); - const results = await provider.provideTimeline(uri, token); + const result = await provider.provideTimeline(uri, cursor, token); // Intentional == we don't know how a provider will respond // eslint-disable-next-line eqeqeq - return results != null - ? results.map(item => convertTimelineItem(item)) - : []; + if (result == null) { + return undefined; + } + + return { + ...result, + source: provider.id, + items: result.items.map(convertTimelineItem) + }; }, dispose() { disposable?.dispose(); diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index 241ee3dabc05..dfdc574d6a09 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -187,7 +187,7 @@ export class TimelinePane extends ViewPane { let request = this._pendingRequests.get(source); request?.tokenSource.dispose(true); - request = this.timelineService.getTimelineRequest(source, this._uri, new CancellationTokenSource())!; + request = this.timelineService.getTimeline(source, this._uri, {}, new CancellationTokenSource())!; this._pendingRequests.set(source, request); request.tokenSource.token.onCancellationRequested(() => this._pendingRequests.delete(source)); @@ -199,7 +199,7 @@ export class TimelinePane extends ViewPane { private async handleRequest(request: TimelineRequest) { let items; try { - items = await this.progressService.withProgress({ location: VIEWLET_ID }, () => request.items); + items = await this.progressService.withProgress({ location: VIEWLET_ID }, () => request.result.then(r => r?.items ?? [])); } catch { } diff --git a/src/vs/workbench/contrib/timeline/common/timeline.ts b/src/vs/workbench/contrib/timeline/common/timeline.ts index 65fe341ffc08..4d3ece3caa75 100644 --- a/src/vs/workbench/contrib/timeline/common/timeline.ts +++ b/src/vs/workbench/contrib/timeline/common/timeline.ts @@ -37,10 +37,24 @@ export interface TimelineChangeEvent { uri?: URI; } +export interface TimelineCursor { + cursor?: any; + before?: boolean; + limit?: number; +} + +export interface Timeline { + source: string; + items: TimelineItemWithSource[]; + + cursor?: any; + more?: boolean; +} + export interface TimelineProvider extends TimelineProviderDescriptor, IDisposable { onDidChange?: Event; - provideTimeline(uri: URI, token: CancellationToken): Promise; + provideTimeline(uri: URI, cursor: TimelineCursor, token: CancellationToken): Promise; } export interface TimelineProviderDescriptor { @@ -55,7 +69,7 @@ export interface TimelineProvidersChangeEvent { } export interface TimelineRequest { - readonly items: Promise; + readonly result: Promise; readonly source: string; readonly tokenSource: CancellationTokenSource; readonly uri: URI; @@ -72,9 +86,7 @@ export interface ITimelineService { getSources(): string[]; - getTimeline(uri: URI, token: CancellationToken): Promise; - - getTimelineRequest(id: string, uri: URI, tokenSource: CancellationTokenSource): TimelineRequest | undefined; + getTimeline(id: string, uri: URI, pagination: TimelineCursor, tokenSource: CancellationTokenSource): TimelineRequest | undefined; } const TIMELINE_SERVICE_ID = 'timeline'; diff --git a/src/vs/workbench/contrib/timeline/common/timelineService.ts b/src/vs/workbench/contrib/timeline/common/timelineService.ts index eda524418318..3fc416e74647 100644 --- a/src/vs/workbench/contrib/timeline/common/timelineService.ts +++ b/src/vs/workbench/contrib/timeline/common/timelineService.ts @@ -3,13 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { Event, Emitter } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; // import { basename } from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; import { ILogService } from 'vs/platform/log/common/log'; -import { ITimelineService, TimelineProvider, TimelineItem, TimelineChangeEvent, TimelineProvidersChangeEvent } from './timeline'; +import { ITimelineService, TimelineChangeEvent, TimelineCursor, TimelineProvidersChangeEvent, TimelineProvider } from './timeline'; export class TimelineService implements ITimelineService { _serviceBrand: undefined; @@ -81,42 +81,7 @@ export class TimelineService implements ITimelineService { return [...this._providers.keys()]; } - async getTimeline(uri: URI, token: CancellationToken, predicate?: (provider: TimelineProvider) => boolean) { - this.logService.trace(`TimelineService#getTimeline(${uri.toString(true)})`); - - const requests: Promise<[string, TimelineItem[]]>[] = []; - - for (const provider of this._providers.values()) { - if (typeof provider.scheme === 'string') { - if (provider.scheme !== '*' && provider.scheme !== uri.scheme) { - continue; - } - } else if (!provider.scheme.includes(uri.scheme)) { - continue; - } - if (!(predicate?.(provider) ?? true)) { - continue; - } - - requests.push(provider.provideTimeline(uri, token).then(p => [provider.id, p])); - } - - const timelines = await Promise.all(requests); - - const timeline = []; - for (const [source, items] of timelines) { - if (items.length === 0) { - continue; - } - - timeline.push(...items.map(item => ({ ...item, source: source }))); - } - - timeline.sort((a, b) => b.timestamp - a.timestamp); - return timeline; - } - - getTimelineRequest(id: string, uri: URI, tokenSource: CancellationTokenSource) { + getTimeline(id: string, uri: URI, cursor: TimelineCursor, tokenSource: CancellationTokenSource) { this.logService.trace(`TimelineService#getTimeline(${id}): uri=${uri.toString(true)}`); const provider = this._providers.get(id); @@ -133,12 +98,16 @@ export class TimelineService implements ITimelineService { } return { - items: provider.provideTimeline(uri, tokenSource.token) - .then(items => { - items = items.map(item => ({ ...item, source: provider.id })); - items.sort((a, b) => (b.timestamp - a.timestamp) || b.source.localeCompare(a.source, undefined, { numeric: true, sensitivity: 'base' })); + result: provider.provideTimeline(uri, cursor, tokenSource.token) + .then(result => { + if (result === undefined) { + return undefined; + } - return items; + result.items = result.items.map(item => ({ ...item, source: provider.id })); + result.items.sort((a, b) => (b.timestamp - a.timestamp) || b.source.localeCompare(a.source, undefined, { numeric: true, sensitivity: 'base' })); + + return result; }), source: provider.id, tokenSource: tokenSource, From 9ae0fd36c91dd523cbc3483e4fbf5c897e4ae0c3 Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Mon, 10 Feb 2020 10:24:33 -0500 Subject: [PATCH 072/109] Enhances timeline - commands, timestamp, etc Adds contributable commands to timeline items Adds right-aligned timestamp to timeline items Adds Open Changes to Git timeline items Adds Copy Commit ID to Git timeline items Adds Copy Commit Message to Git timeline items --- extensions/git/package.json | 54 +- extensions/git/package.nls.json | 3 + extensions/git/src/commands.ts | 39 +- extensions/git/src/timelineProvider.ts | 93 ++-- extensions/git/yarn.lock | 504 +----------------- src/vs/base/common/date.ts | 47 ++ src/vs/platform/actions/common/actions.ts | 4 +- .../api/browser/mainThreadTimeline.ts | 4 +- .../workbench/api/common/extHost.api.impl.ts | 2 +- .../workbench/api/common/extHost.protocol.ts | 2 +- .../workbench/api/common/extHostTimeline.ts | 128 +++-- .../api/common/menusExtensionPoint.ts | 12 + .../timeline/browser/media/timelinePane.css | 17 + .../contrib/timeline/browser/timelinePane.ts | 231 ++++++-- .../contrib/timeline/common/timeline.ts | 14 +- .../timeline/common/timelineService.ts | 4 +- 16 files changed, 535 insertions(+), 623 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index c1cd8b5135c0..b1ae36f57a7f 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -447,6 +447,22 @@ "command": "git.stashDrop", "title": "%command.stashDrop%", "category": "Git" + }, + { + "command": "git.timeline.openDiff", + "title": "%command.timelineOpenDiff%", + "icon": "$(compare-changes)", + "category": "Git" + }, + { + "command": "git.timeline.copyCommitId", + "title": "%command.timelineCopyCommitId%", + "category": "Git" + }, + { + "command": "git.timeline.copyCommitMessage", + "title": "%command.timelineCopyCommitMessage%", + "category": "Git" } ], "menus": { @@ -718,6 +734,18 @@ { "command": "git.stashDrop", "when": "config.git.enabled && gitOpenRepositoryCount != 0" + }, + { + "command": "git.timeline.openDiff", + "when": "false" + }, + { + "command": "git.timeline.copyCommitId", + "when": "false" + }, + { + "command": "git.timeline.copyCommitMessage", + "when": "false" } ], "scm/title": [ @@ -1248,6 +1276,28 @@ "command": "git.revertChange", "when": "originalResourceScheme == git" } + ], + "timeline/item/context": [ + { + "command": "git.timeline.openDiff", + "group": "inline", + "when": "timelineItem =~ /git:file\\b/" + }, + { + "command": "git.timeline.openDiff", + "group": "1_timeline", + "when": "timelineItem =~ /git:file\\b/" + }, + { + "command": "git.timeline.copyCommitId", + "group": "2_timeline@1", + "when": "timelineItem =~ /git:file:commit\\b/" + }, + { + "command": "git.timeline.copyCommitMessage", + "group": "2_timeline@2", + "when": "timelineItem =~ /git:file:commit\\b/" + } ] }, "configuration": { @@ -1779,10 +1829,10 @@ "@types/file-type": "^5.2.1", "@types/mocha": "2.2.43", "@types/node": "^12.11.7", + "@types/vscode": "^1.42", "@types/which": "^1.0.28", "mocha": "^3.2.0", "mocha-junit-reporter": "^1.23.3", - "mocha-multi-reporters": "^1.1.7", - "vscode": "^1.1.36" + "mocha-multi-reporters": "^1.1.7" } } diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index f51154cebf69..534ec69429ac 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -70,6 +70,9 @@ "command.stashApply": "Apply Stash...", "command.stashApplyLatest": "Apply Latest Stash", "command.stashDrop": "Drop Stash...", + "command.timelineOpenDiff": "Open Changes", + "command.timelineCopyCommitId": "Copy Commit ID", + "command.timelineCopyCommitMessage": "Copy Commit Message", "config.enabled": "Whether git is enabled.", "config.path": "Path and filename of the git executable, e.g. `C:\\Program Files\\Git\\bin\\git.exe` (Windows).", "config.autoRepositoryDetection": "Configures when repositories should be automatically detected.", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index dba21595ea8a..9d8fe2a4ef46 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -6,7 +6,7 @@ import { lstat, Stats } from 'fs'; import * as os from 'os'; import * as path from 'path'; -import { commands, Disposable, LineChange, MessageOptions, OutputChannel, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder } from 'vscode'; +import { commands, Disposable, LineChange, MessageOptions, OutputChannel, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env } from 'vscode'; import TelemetryReporter from 'vscode-extension-telemetry'; import * as nls from 'vscode-nls'; import { Branch, GitErrorCodes, Ref, RefType, Status, CommitOptions } from './api/git'; @@ -17,6 +17,7 @@ import { applyLineChanges, getModifiedRange, intersectDiffWithRange, invertLineC import { fromGitUri, toGitUri, isGitUri } from './uri'; import { grep, isDescendant, pathEquals } from './util'; import { Log, LogLevel } from './log'; +import { GitTimelineItem } from './timelineProvider'; const localize = nls.loadMessageBundle(); @@ -2331,23 +2332,47 @@ export class CommandCenter { return result && result.stash; } - @command('git.openDiff', { repository: false }) - async openDiff(uri: Uri, lhs: string, rhs: string) { + @command('git.timeline.openDiff', { repository: false }) + async timelineOpenDiff(item: TimelineItem, uri: Uri | undefined, _source: string) { + // eslint-disable-next-line eqeqeq + if (uri == null || !GitTimelineItem.is(item)) { + return undefined; + } + const basename = path.basename(uri.fsPath); let title; - if ((lhs === 'HEAD' || lhs === '~') && rhs === '') { + if ((item.previousRef === 'HEAD' || item.previousRef === '~') && item.ref === '') { title = `${basename} (Working Tree)`; } - else if (lhs === 'HEAD' && rhs === '~') { + else if (item.previousRef === 'HEAD' && item.ref === '~') { title = `${basename} (Index)`; } else { - title = `${basename} (${lhs.endsWith('^') ? `${lhs.substr(0, 8)}^` : lhs.substr(0, 8)}) \u27f7 ${basename} (${rhs.endsWith('^') ? `${rhs.substr(0, 8)}^` : rhs.substr(0, 8)})`; + title = `${basename} (${item.shortPreviousRef}) \u27f7 ${basename} (${item.shortRef})`; } - return commands.executeCommand('vscode.diff', toGitUri(uri, lhs), rhs === '' ? uri : toGitUri(uri, rhs), title); + return commands.executeCommand('vscode.diff', toGitUri(uri, item.previousRef), item.ref === '' ? uri : toGitUri(uri, item.ref), title); } + @command('git.timeline.copyCommitId', { repository: false }) + async timelineCopyCommitId(item: TimelineItem, _uri: Uri | undefined, _source: string) { + if (!GitTimelineItem.is(item)) { + return; + } + + env.clipboard.writeText(item.ref); + } + + @command('git.timeline.copyCommitMessage', { repository: false }) + async timelineCopyCommitMessage(item: TimelineItem, _uri: Uri | undefined, _source: string) { + if (!GitTimelineItem.is(item)) { + return; + } + + env.clipboard.writeText(item.message); + } + + private createCommand(id: string, key: string, method: Function, options: CommandOptions): (...args: any[]) => any { const result = (...args: any[]) => { let result: Promise; diff --git a/extensions/git/src/timelineProvider.ts b/extensions/git/src/timelineProvider.ts index a83436dc29eb..9db3495aec73 100644 --- a/extensions/git/src/timelineProvider.ts +++ b/extensions/git/src/timelineProvider.ts @@ -5,7 +5,6 @@ import * as dayjs from 'dayjs'; import * as advancedFormat from 'dayjs/plugin/advancedFormat'; -import * as relativeTime from 'dayjs/plugin/relativeTime'; import { CancellationToken, Disposable, Event, EventEmitter, ThemeIcon, Timeline, TimelineChangeEvent, TimelineCursor, TimelineItem, TimelineProvider, Uri, workspace } from 'vscode'; import { Model } from './model'; import { Repository } from './repository'; @@ -13,11 +12,55 @@ import { debounce } from './decorators'; import { Status } from './api/git'; dayjs.extend(advancedFormat); -dayjs.extend(relativeTime); // TODO[ECA]: Localize all the strings // TODO[ECA]: Localize or use a setting for date format +export class GitTimelineItem extends TimelineItem { + static is(item: TimelineItem): item is GitTimelineItem { + return item instanceof GitTimelineItem; + } + + readonly ref: string; + readonly previousRef: string; + readonly message: string; + + constructor( + ref: string, + previousRef: string, + message: string, + timestamp: number, + id: string, + contextValue: string + ) { + const index = message.indexOf('\n'); + const label = index !== -1 ? `${message.substring(0, index)} \u2026` : message; + + super(label, timestamp); + + this.ref = ref; + this.previousRef = previousRef; + this.message = message; + this.id = id; + this.contextValue = contextValue; + } + + get shortRef() { + return this.shortenRef(this.ref); + } + + get shortPreviousRef() { + return this.shortenRef(this.previousRef); + } + + private shortenRef(ref: string): string { + if (ref === '' || ref === '~' || ref === 'HEAD') { + return ref; + } + return ref.endsWith('^') ? `${ref.substr(0, 8)}^` : ref.substr(0, 8); + } +} + export class GitTimelineProvider implements TimelineProvider { private _onDidChange = new EventEmitter(); get onDidChange(): Event { @@ -72,25 +115,17 @@ export class GitTimelineProvider implements TimelineProvider { const commits = await repo.logFile(uri); let dateFormatter: dayjs.Dayjs; - const items = commits.map(c => { - let message = c.message; - - const index = message.indexOf('\n'); - if (index !== -1) { - message = `${message.substring(0, index)} \u2026`; - } - + const items = commits.map(c => { dateFormatter = dayjs(c.authorDate); - const item = new TimelineItem(message, c.authorDate?.getTime() ?? 0); - item.id = c.hash; + const item = new GitTimelineItem(c.hash, `${c.hash}^`, c.message, c.authorDate?.getTime() ?? 0, c.hash, 'git:file:commit'); item.iconPath = new (ThemeIcon as any)('git-commit'); - item.description = `${dateFormatter.fromNow()} \u2022 ${c.authorName}`; - item.detail = `${c.authorName} (${c.authorEmail}) \u2014 ${c.hash.substr(0, 8)}\n${dateFormatter.fromNow()} (${dateFormatter.format('MMMM Do, YYYY h:mma')})\n\n${c.message}`; + item.description = c.authorName; + item.detail = `${c.authorName} (${c.authorEmail}) \u2014 ${c.hash.substr(0, 8)}\n${dateFormatter.format('MMMM Do, YYYY h:mma')}\n\n${c.message}`; item.command = { - title: 'Open Diff', - command: 'git.openDiff', - arguments: [uri, `${c.hash}^`, c.hash] + title: 'Open Comparison', + command: 'git.timeline.openDiff', + arguments: [uri, this.id, item] }; return item; @@ -123,16 +158,15 @@ export class GitTimelineProvider implements TimelineProvider { break; } - const item = new TimelineItem('Staged Changes', date.getTime()); - item.id = 'index'; + const item = new GitTimelineItem('~', 'HEAD', 'Staged Changes', date.getTime(), 'index', 'git:file:index'); // TODO[ECA]: Replace with a better icon -- reflecting its status maybe? item.iconPath = new (ThemeIcon as any)('git-commit'); - item.description = `${dateFormatter.fromNow()} \u2022 You`; - item.detail = `You \u2014 Index\n${dateFormatter.fromNow()} (${dateFormatter.format('MMMM Do, YYYY h:mma')})\n${status}`; + item.description = 'You'; + item.detail = `You \u2014 Index\n${dateFormatter.format('MMMM Do, YYYY h:mma')}\n${status}`; item.command = { title: 'Open Comparison', - command: 'git.openDiff', - arguments: [uri, 'HEAD', '~'] + command: 'git.timeline.openDiff', + arguments: [uri, this.id, item] }; items.push(item); @@ -166,16 +200,15 @@ export class GitTimelineProvider implements TimelineProvider { break; } - const item = new TimelineItem('Uncommited Changes', date.getTime()); - item.id = 'working'; + const item = new GitTimelineItem('', index ? '~' : 'HEAD', 'Uncommited Changes', date.getTime(), 'working', 'git:file:working'); // TODO[ECA]: Replace with a better icon -- reflecting its status maybe? item.iconPath = new (ThemeIcon as any)('git-commit'); - item.description = `${dateFormatter.fromNow()} \u2022 You`; - item.detail = `You \u2014 Working Tree\n${dateFormatter.fromNow()} (${dateFormatter.format('MMMM Do, YYYY h:mma')})\n${status}`; + item.description = 'You'; + item.detail = `You \u2014 Working Tree\n${dateFormatter.format('MMMM Do, YYYY h:mma')}\n${status}`; item.command = { title: 'Open Comparison', - command: 'git.openDiff', - arguments: [uri, index ? '~' : 'HEAD', ''] + command: 'git.timeline.openDiff', + arguments: [uri, this.id, item] }; items.push(item); @@ -208,6 +241,6 @@ export class GitTimelineProvider implements TimelineProvider { @debounce(500) private fireChanged() { - this._onDidChange.fire(); + this._onDidChange.fire({}); } } diff --git a/extensions/git/yarn.lock b/extensions/git/yarn.lock index af5b10127dc4..ff2f89944877 100644 --- a/extensions/git/yarn.lock +++ b/extensions/git/yarn.lock @@ -31,28 +31,16 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-12.11.7.tgz#57682a9771a3f7b09c2497f28129a0462966524a" integrity sha512-JNbGaHFCLwgHn/iCckiGSOZ1XYHsKFwREtzPwSGCVld1SGhOlmZw2D4ZI94HQCrBHbADzW9m4LER/8olJTRGHA== +"@types/vscode@^1.42": + version "1.42.0" + resolved "https://registry.yarnpkg.com/@types/vscode/-/vscode-1.42.0.tgz#0ad891a9487e91e34be7c56985058a179031eb76" + integrity sha512-ds6TceMsh77Fs0Mq0Vap6Y72JbGWB8Bay4DrnJlf5d9ui2RSe1wis13oQm+XhguOeH1HUfLGzaDAoupTUtgabw== + "@types/which@^1.0.28": version "1.0.28" resolved "https://registry.yarnpkg.com/@types/which/-/which-1.0.28.tgz#016e387629b8817bed653fe32eab5d11279c8df6" integrity sha1-AW44dim4gXvtZT/jLqtdESecjfY= -agent-base@4, agent-base@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" - integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg== - dependencies: - es6-promisify "^5.0.0" - -ajv@^6.5.5: - version "6.11.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.11.0.tgz#c3607cbc8ae392d8a5a536f25b21f8e5f3f87fe9" - integrity sha512-nCprB/0syFYy9fVYU1ox1l2KN8S9I+tziH8D4zdZuLT3N6RMlGSGt5FSTpAiHB/Whv8Qs1cWHma1aMKZyaHRKA== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - ansi-regex@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" @@ -67,45 +55,11 @@ applicationinsights@1.0.8: diagnostic-channel-publishers "0.2.1" zone.js "0.7.6" -asn1@~0.2.3: - version "0.2.4" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" - integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== - dependencies: - safer-buffer "~2.1.0" - -assert-plus@1.0.0, assert-plus@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" - integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= - -aws-sign2@~0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" - integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= - -aws4@^1.8.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e" - integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug== - balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= -bcrypt-pbkdf@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" - integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= - dependencies: - tweetnacl "^0.14.3" - brace-expansion@^1.1.7: version "1.1.8" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" @@ -119,43 +73,16 @@ browser-stdout@1.3.0: resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f" integrity sha1-81HTKWnTL6XXpVZxVCY9korjvR8= -browser-stdout@1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" - integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== - -buffer-from@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" - integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== - byline@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1" integrity sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE= -caseless@~0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" - integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= - charenc@~0.0.1: version "0.0.2" resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc= -combined-stream@^1.0.6, combined-stream@~1.0.6: - version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - -commander@2.15.1: - version "2.15.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" - integrity sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag== - commander@2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" @@ -168,23 +95,11 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -core-util-is@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= - crypt@~0.0.1: version "0.0.2" resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs= -dashdash@^1.12.0: - version "1.14.1" - resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" - integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= - dependencies: - assert-plus "^1.0.0" - dayjs@1.8.19: version "1.8.19" resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.8.19.tgz#5117dc390d8f8e586d53891dbff3fa308f51abfe" @@ -197,13 +112,6 @@ debug@2.6.8: dependencies: ms "2.0.0" -debug@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" - integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== - dependencies: - ms "2.0.0" - debug@^2.2.0: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -218,11 +126,6 @@ debug@^3.1.0: dependencies: ms "^2.1.1" -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= - diagnostic-channel-publishers@0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz#8e2d607a8b6d79fe880b548bc58cc6beb288c4f3" @@ -240,92 +143,21 @@ diff@3.2.0: resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9" integrity sha1-yc45Okt8vQsFinJck98pkCeGj/k= -diff@3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" - integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== - -ecc-jsbn@~0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" - integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= - dependencies: - jsbn "~0.1.0" - safer-buffer "^2.1.0" - -es6-promise@^4.0.3: - version "4.2.8" - resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" - integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== - -es6-promisify@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" - integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= - dependencies: - es6-promise "^4.0.3" - escape-string-regexp@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= -extend@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - -extsprintf@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" - integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= - -extsprintf@^1.2.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" - integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= - -fast-deep-equal@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4" - integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA== - -fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - file-type@^7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/file-type/-/file-type-7.2.0.tgz#113cfed52e1d6959ab80248906e2f25a8cdccb74" integrity sha1-ETz+1S4daVmrgCSJBuLyWozcy3Q= -forever-agent@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" - integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= - -form-data@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" - integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.6" - mime-types "^2.1.12" - fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -getpass@^0.1.1: - version "0.1.7" - resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" - integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= - dependencies: - assert-plus "^1.0.0" - glob@7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" @@ -338,98 +170,26 @@ glob@7.1.1: once "^1.3.0" path-is-absolute "^1.0.0" -glob@7.1.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" - integrity sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^7.1.2: - version "7.1.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - "graceful-readlink@>= 1.0.0": version "1.0.1" resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" integrity sha1-TK+tdrxi8C+gObL5Tpo906ORpyU= -growl@1.10.5: - version "1.10.5" - resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" - integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== - growl@1.9.2: version "1.9.2" resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f" integrity sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8= -har-schema@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" - integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= - -har-validator@~5.1.0: - version "5.1.3" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" - integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== - dependencies: - ajv "^6.5.5" - har-schema "^2.0.0" - has-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" integrity sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo= -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= - he@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0= -http-proxy-agent@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405" - integrity sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg== - dependencies: - agent-base "4" - debug "3.1.0" - -http-signature@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" - integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= - dependencies: - assert-plus "^1.0.0" - jsprim "^1.2.2" - sshpk "^1.7.0" - -https-proxy-agent@^2.2.1: - version "2.2.4" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b" - integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg== - dependencies: - agent-base "^4.3.0" - debug "^3.1.0" - iconv-lite@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -455,61 +215,21 @@ is-buffer@~1.1.1: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== -is-typedarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= - isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= -isstream@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" - integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= - -jsbn@~0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" - integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= - jschardet@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-2.1.1.tgz#af6f8fd0b3b0f5d46a8fd9614a4fce490575c184" integrity sha512-pA5qG9Zwm8CBpGlK/lo2GE9jPxwqRgMV7Lzc/1iaPccw6v4Rhj8Zg2BTyrdmHmxlJojnbLupLeRnaPLsq03x6Q== -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-schema@0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" - integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= - -json-stringify-safe@~5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" - integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= - json3@3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1" integrity sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE= -jsprim@^1.2.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" - integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= - dependencies: - assert-plus "1.0.0" - extsprintf "1.3.0" - json-schema "0.2.3" - verror "1.10.0" - lodash._baseassign@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz#8c38a099500f215ad09e59f1722fd0c52bfe0a4e" @@ -580,19 +300,7 @@ md5@^2.1.0: crypt "~0.0.1" is-buffer "~1.1.1" -mime-db@1.43.0: - version "1.43.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58" - integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ== - -mime-types@^2.1.12, mime-types@~2.1.19: - version "2.1.26" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06" - integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ== - dependencies: - mime-db "1.43.0" - -minimatch@3.0.4, minimatch@^3.0.2, minimatch@^3.0.4: +minimatch@^3.0.2: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== @@ -648,23 +356,6 @@ mocha@^3.2.0: mkdirp "0.5.1" supports-color "3.1.2" -mocha@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-5.2.0.tgz#6d8ae508f59167f940f2b5b3c4a612ae50c90ae6" - integrity sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ== - dependencies: - browser-stdout "1.3.1" - commander "2.15.1" - debug "3.1.0" - diff "3.5.0" - escape-string-regexp "1.0.5" - glob "7.1.2" - growl "1.10.5" - he "1.1.1" - minimatch "3.0.4" - mkdirp "0.5.1" - supports-color "5.4.0" - ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -675,11 +366,6 @@ ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -oauth-sign@~0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" - integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== - once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -692,73 +378,7 @@ path-is-absolute@^1.0.0: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= -performance-now@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" - integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= - -psl@^1.1.24: - version "1.7.0" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.7.0.tgz#f1c4c47a8ef97167dea5d6bbf4816d736e884a3c" - integrity sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ== - -punycode@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= - -punycode@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== - -qs@~6.5.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" - integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== - -querystringify@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e" - integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA== - -request@^2.88.0: - version "2.88.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" - integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.8.0" - caseless "~0.12.0" - combined-stream "~1.0.6" - extend "~3.0.2" - forever-agent "~0.6.1" - form-data "~2.3.2" - har-validator "~5.1.0" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.19" - oauth-sign "~0.9.0" - performance-now "^2.1.0" - qs "~6.5.2" - safe-buffer "^5.1.2" - tough-cookie "~2.4.3" - tunnel-agent "^0.6.0" - uuid "^3.3.2" - -requires-port@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" - integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= - -safe-buffer@^5.0.1, safe-buffer@^5.1.2: - version "5.2.0" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" - integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== - -"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: +"safer-buffer@>= 2.1.2 < 3": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -768,39 +388,6 @@ semver@^5.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" integrity sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA== -semver@^5.4.1: - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== - -source-map-support@^0.5.0: - version "0.5.16" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042" - integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@^0.6.0: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -sshpk@^1.7.0: - version "1.16.1" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" - integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== - dependencies: - asn1 "~0.2.3" - assert-plus "^1.0.0" - bcrypt-pbkdf "^1.0.0" - dashdash "^1.12.0" - ecc-jsbn "~0.1.1" - getpass "^0.1.1" - jsbn "~0.1.0" - safer-buffer "^2.0.2" - tweetnacl "~0.14.0" - strip-ansi@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" @@ -815,62 +402,6 @@ supports-color@3.1.2: dependencies: has-flag "^1.0.0" -supports-color@5.4.0: - version "5.4.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54" - integrity sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w== - dependencies: - has-flag "^3.0.0" - -tough-cookie@~2.4.3: - version "2.4.3" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" - integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ== - dependencies: - psl "^1.1.24" - punycode "^1.4.1" - -tunnel-agent@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" - integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= - dependencies: - safe-buffer "^5.0.1" - -tweetnacl@^0.14.3, tweetnacl@~0.14.0: - version "0.14.5" - resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" - integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= - -uri-js@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" - integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== - dependencies: - punycode "^2.1.0" - -url-parse@^1.4.4: - version "1.4.7" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278" - integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg== - dependencies: - querystringify "^2.1.1" - requires-port "^1.0.0" - -uuid@^3.3.2: - version "3.4.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" - integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== - -verror@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" - integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= - dependencies: - assert-plus "^1.0.0" - core-util-is "1.0.2" - extsprintf "^1.2.0" - vscode-extension-telemetry@0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.1.1.tgz#91387e06b33400c57abd48979b0e790415ae110b" @@ -883,32 +414,11 @@ vscode-nls@^4.0.0: resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.0.0.tgz#4001c8a6caba5cedb23a9c5ce1090395c0e44002" integrity sha512-qCfdzcH+0LgQnBpZA53bA32kzp9rpq/f66Som577ObeuDlFIrtbEJ+A/+CCxjIh4G8dpJYNCKIsxpRAHIfsbNw== -vscode-test@^0.4.1: - version "0.4.3" - resolved "https://registry.yarnpkg.com/vscode-test/-/vscode-test-0.4.3.tgz#461ebf25fc4bc93d77d982aed556658a2e2b90b8" - integrity sha512-EkMGqBSefZH2MgW65nY05rdRSko15uvzq4VAPM5jVmwYuFQKE7eikKXNJDRxL+OITXHB6pI+a3XqqD32Y3KC5w== - dependencies: - http-proxy-agent "^2.1.0" - https-proxy-agent "^2.2.1" - vscode-uri@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-2.0.0.tgz#2df704222f72b8a71ff266ba0830ed6c51ac1542" integrity sha512-lWXWofDSYD8r/TIyu64MdwB4FaSirQ608PP/TzUyslyOeHGwQ0eTHUZeJrK1ILOmwUHaJtV693m2JoUYroUDpw== -vscode@^1.1.36: - version "1.1.36" - resolved "https://registry.yarnpkg.com/vscode/-/vscode-1.1.36.tgz#5e1a0d1bf4977d0c7bc5159a9a13d5b104d4b1b6" - integrity sha512-cGFh9jmGLcTapCpPCKvn8aG/j9zVQ+0x5hzYJq5h5YyUXVGa1iamOaB2M2PZXoumQPES4qeAP1FwkI0b6tL4bQ== - dependencies: - glob "^7.1.2" - mocha "^5.2.0" - request "^2.88.0" - semver "^5.4.1" - source-map-support "^0.5.0" - url-parse "^1.4.4" - vscode-test "^0.4.1" - which@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" diff --git a/src/vs/base/common/date.ts b/src/vs/base/common/date.ts index a6da9d5ae336..acfc19d6edb3 100644 --- a/src/vs/base/common/date.ts +++ b/src/vs/base/common/date.ts @@ -5,6 +5,53 @@ import { pad } from './strings'; +const minute = 60; +const hour = minute * 60; +const day = hour * 24; +const week = day * 7; +const month = day * 30; +const year = day * 365; + +// TODO[ECA]: Localize strings +export function fromNow(date: number | Date) { + if (typeof date !== 'number') { + date = date.getTime(); + } + + const seconds = Math.round((new Date().getTime() - date) / 1000); + if (seconds < 30) { + return 'now'; + } + + let value: number; + let unit: string; + if (seconds < minute) { + value = seconds; + unit = 'sec'; + } else if (seconds < hour) { + value = Math.floor(seconds / minute); + unit = 'min'; + } else if (seconds < day) { + value = Math.floor(seconds / hour); + unit = 'hr'; + } else if (seconds < week) { + value = Math.floor(seconds / day); + unit = 'day'; + } else if (seconds < month) { + value = Math.floor(seconds / week); + unit = 'wk'; + } else if (seconds < year) { + value = Math.floor(seconds / month); + unit = 'mo'; + } else { + value = Math.floor(seconds / year); + unit = 'yr'; + } + + return `${value} ${unit}${value === 1 ? '' : 's'}`; + +} + export function toLocalISOString(date: Date): string { return date.getFullYear() + '-' + pad(date.getMonth() + 1, 2) + diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 25a4db0972c4..c30c37e13943 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -114,7 +114,9 @@ export class MenuId { static readonly CommentActions = new MenuId('CommentActions'); static readonly BulkEditTitle = new MenuId('BulkEditTitle'); static readonly BulkEditContext = new MenuId('BulkEditContext'); - + static readonly TimelineItemContext = new MenuId('TimelineItemContext'); + static readonly TimelineTitle = new MenuId('TimelineTitle'); + static readonly TimelineTitleContext = new MenuId('TimelineTitleContext'); readonly id: number; readonly _debugName: string; diff --git a/src/vs/workbench/api/browser/mainThreadTimeline.ts b/src/vs/workbench/api/browser/mainThreadTimeline.ts index f65a89b432c9..428bf0ed2d95 100644 --- a/src/vs/workbench/api/browser/mainThreadTimeline.ts +++ b/src/vs/workbench/api/browser/mainThreadTimeline.ts @@ -39,8 +39,8 @@ export class MainThreadTimeline implements MainThreadTimelineShape { this._timelineService.registerTimelineProvider({ ...provider, onDidChange: onDidChange.event, - provideTimeline(uri: URI, cursor: TimelineCursor, token: CancellationToken) { - return proxy.$getTimeline(provider.id, uri, cursor, token); + provideTimeline(uri: URI, cursor: TimelineCursor, token: CancellationToken, options?: { cacheResults?: boolean }) { + return proxy.$getTimeline(provider.id, uri, cursor, token, options); }, dispose() { emitters.delete(provider.id); diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 61353fa26834..0d25c1176e75 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -133,7 +133,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostLabelService = rpcProtocol.set(ExtHostContext.ExtHostLabelService, new ExtHostLabelService(rpcProtocol)); const extHostTheming = rpcProtocol.set(ExtHostContext.ExtHostTheming, new ExtHostTheming(rpcProtocol)); const extHostAuthentication = rpcProtocol.set(ExtHostContext.ExtHostAuthentication, new ExtHostAuthentication(rpcProtocol)); - const extHostTimeline = rpcProtocol.set(ExtHostContext.ExtHostTimeline, new ExtHostTimeline(rpcProtocol)); + const extHostTimeline = rpcProtocol.set(ExtHostContext.ExtHostTimeline, new ExtHostTimeline(rpcProtocol, extHostCommands)); // Check that no named customers are missing const expected: ProxyIdentifier[] = values(ExtHostContext); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 21d4884818ee..b780bccbd4d4 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1457,7 +1457,7 @@ export interface ExtHostTunnelServiceShape { } export interface ExtHostTimelineShape { - $getTimeline(source: string, uri: UriComponents, cursor: TimelineCursor, token: CancellationToken): Promise; + $getTimeline(source: string, uri: UriComponents, cursor: TimelineCursor, token: CancellationToken, options?: { cacheResults?: boolean }): Promise; } // --- proxy identifiers diff --git a/src/vs/workbench/api/common/extHostTimeline.ts b/src/vs/workbench/api/common/extHostTimeline.ts index c4e3832649a6..f5f63a2ce4dc 100644 --- a/src/vs/workbench/api/common/extHostTimeline.ts +++ b/src/vs/workbench/api/common/extHostTimeline.ts @@ -7,55 +7,77 @@ import * as vscode from 'vscode'; import { UriComponents, URI } from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ExtHostTimelineShape, MainThreadTimelineShape, IMainContext, MainContext } from 'vs/workbench/api/common/extHost.protocol'; -import { Timeline, TimelineCursor, TimelineItemWithSource, TimelineProvider } from 'vs/workbench/contrib/timeline/common/timeline'; +import { Timeline, TimelineCursor, TimelineItem, TimelineProvider } from 'vs/workbench/contrib/timeline/common/timeline'; import { IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { CommandsConverter } from 'vs/workbench/api/common/extHostCommands'; +import { CommandsConverter, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { ThemeIcon } from 'vs/workbench/api/common/extHostTypes'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; export interface IExtHostTimeline extends ExtHostTimelineShape { readonly _serviceBrand: undefined; - $getTimeline(id: string, uri: UriComponents, cursor: vscode.TimelineCursor, token: vscode.CancellationToken): Promise; + $getTimeline(id: string, uri: UriComponents, cursor: vscode.TimelineCursor, token: vscode.CancellationToken, options?: { cacheResults?: boolean }): Promise; } export const IExtHostTimeline = createDecorator('IExtHostTimeline'); export class ExtHostTimeline implements IExtHostTimeline { + private static handlePool = 0; + _serviceBrand: undefined; private _proxy: MainThreadTimelineShape; private _providers = new Map(); + private _itemsBySourceByUriMap = new Map>>(); + constructor( mainContext: IMainContext, + commands: ExtHostCommands, ) { this._proxy = mainContext.getProxy(MainContext.MainThreadTimeline); + + commands.registerArgumentProcessor({ + processArgument: arg => { + if (arg && arg.$mid === 11) { + const uri = arg.uri === undefined ? undefined : URI.revive(arg.uri); + return this._itemsBySourceByUriMap.get(getUriKey(uri))?.get(arg.source)?.get(arg.handle); + } + + return arg; + } + }); } - async $getTimeline(id: string, uri: UriComponents, cursor: vscode.TimelineCursor, token: vscode.CancellationToken): Promise { + async $getTimeline(id: string, uri: UriComponents, cursor: vscode.TimelineCursor, token: vscode.CancellationToken, options?: { cacheResults?: boolean }): Promise { const provider = this._providers.get(id); - return provider?.provideTimeline(URI.revive(uri), cursor, token); + return provider?.provideTimeline(URI.revive(uri), cursor, token, options); } - registerTimelineProvider(scheme: string | string[], provider: vscode.TimelineProvider, extensionId: ExtensionIdentifier, commandConverter: CommandsConverter): IDisposable { + registerTimelineProvider(scheme: string | string[], provider: vscode.TimelineProvider, _extensionId: ExtensionIdentifier, commandConverter: CommandsConverter): IDisposable { const timelineDisposables = new DisposableStore(); - const convertTimelineItem = this.convertTimelineItem(provider.id, commandConverter, timelineDisposables); + const convertTimelineItem = this.convertTimelineItem(provider.id, commandConverter, timelineDisposables).bind(this); let disposable: IDisposable | undefined; if (provider.onDidChange) { disposable = provider.onDidChange(this.emitTimelineChangeEvent(provider.id), this); } + const itemsBySourceByUriMap = this._itemsBySourceByUriMap; return this.registerTimelineProviderCore({ ...provider, scheme: scheme, onDidChange: undefined, - async provideTimeline(uri: URI, cursor: TimelineCursor, token: CancellationToken) { + async provideTimeline(uri: URI, cursor: TimelineCursor, token: CancellationToken, options?: { cacheResults?: boolean }) { timelineDisposables.clear(); + // For now, only allow the caching of a single Uri + if (options?.cacheResults && !itemsBySourceByUriMap.has(getUriKey(uri))) { + itemsBySourceByUriMap.clear(); + } + const result = await provider.provideTimeline(uri, cursor, token); // Intentional == we don't know how a provider will respond // eslint-disable-next-line eqeqeq @@ -63,10 +85,12 @@ export class ExtHostTimeline implements IExtHostTimeline { return undefined; } + // TODO: Determine if we should cache dependent on who calls us (internal vs external) + const convertItem = convertTimelineItem(uri, options?.cacheResults ?? false); return { ...result, source: provider.id, - items: result.items.map(convertTimelineItem) + items: result.items.map(convertItem) }; }, dispose() { @@ -76,39 +100,72 @@ export class ExtHostTimeline implements IExtHostTimeline { }); } - private convertTimelineItem(source: string, commandConverter: CommandsConverter, disposables: DisposableStore): (item: vscode.TimelineItem) => TimelineItemWithSource { - return (item: vscode.TimelineItem) => { - const { iconPath, ...props } = item; + private convertTimelineItem(source: string, commandConverter: CommandsConverter, disposables: DisposableStore) { + return (uri: URI, cacheResults: boolean) => { + let itemsMap: Map | undefined; + if (cacheResults) { + const uriKey = getUriKey(uri); - let icon; - let iconDark; - let themeIcon; - if (item.iconPath) { - if (iconPath instanceof ThemeIcon) { - themeIcon = { id: iconPath.id }; + let sourceMap = this._itemsBySourceByUriMap.get(uriKey); + if (sourceMap === undefined) { + sourceMap = new Map(); + this._itemsBySourceByUriMap.set(uriKey, sourceMap); } - else if (URI.isUri(iconPath)) { - icon = iconPath; - iconDark = iconPath; - } - else { - ({ light: icon, dark: iconDark } = iconPath as { light: URI; dark: URI }); + + itemsMap = sourceMap.get(source); + if (itemsMap === undefined) { + itemsMap = new Map(); + sourceMap.set(source, itemsMap); } } - return { - ...props, - source: source, - command: item.command ? commandConverter.toInternal(item.command, disposables) : undefined, - icon: icon, - iconDark: iconDark, - themeIcon: themeIcon + return (item: vscode.TimelineItem): TimelineItem => { + const { iconPath, ...props } = item; + + const handle = `${source}|${item.id ?? `${item.timestamp}-${ExtHostTimeline.handlePool++}`}`; + itemsMap?.set(handle, item); + + let icon; + let iconDark; + let themeIcon; + if (item.iconPath) { + if (iconPath instanceof ThemeIcon) { + themeIcon = { id: iconPath.id }; + } + else if (URI.isUri(iconPath)) { + icon = iconPath; + iconDark = iconPath; + } + else { + ({ light: icon, dark: iconDark } = iconPath as { light: URI; dark: URI }); + } + } + + return { + ...props, + handle: handle, + source: source, + command: item.command ? commandConverter.toInternal(item.command, disposables) : undefined, + icon: icon, + iconDark: iconDark, + themeIcon: themeIcon + }; }; }; } private emitTimelineChangeEvent(id: string) { return (e: vscode.TimelineChangeEvent) => { + // Clear caches + if (e?.uri === undefined) { + for (const sourceMap of this._itemsBySourceByUriMap.values()) { + sourceMap.get(id)?.clear(); + } + } + else { + this._itemsBySourceByUriMap.get(getUriKey(e.uri))?.clear(); + } + this._proxy.$emitTimelineChangeEvent({ ...e, id: id }); }; } @@ -129,9 +186,18 @@ export class ExtHostTimeline implements IExtHostTimeline { this._providers.set(provider.id, provider); return toDisposable(() => { + for (const sourceMap of this._itemsBySourceByUriMap.values()) { + sourceMap.get(provider.id)?.clear(); + } + this._providers.delete(provider.id); this._proxy.$unregisterTimelineProvider(provider.id); provider.dispose(); }); } } + +function getUriKey(uri: URI | undefined): string | undefined { + return uri?.toString(); +} + diff --git a/src/vs/workbench/api/common/menusExtensionPoint.ts b/src/vs/workbench/api/common/menusExtensionPoint.ts index c79a3c16ab05..ab474c9825f7 100644 --- a/src/vs/workbench/api/common/menusExtensionPoint.ts +++ b/src/vs/workbench/api/common/menusExtensionPoint.ts @@ -52,6 +52,8 @@ namespace schema { case 'comments/comment/title': return MenuId.CommentTitle; case 'comments/comment/context': return MenuId.CommentActions; case 'extension/context': return MenuId.ExtensionContext; + case 'timeline/title': return MenuId.TimelineTitle; + case 'timeline/item/context': return MenuId.TimelineItemContext; } return undefined; @@ -215,6 +217,16 @@ namespace schema { type: 'array', items: menuItem }, + 'timeline/title': { + description: localize('view.timelineTitle', "The Timeline view title menu"), + type: 'array', + items: menuItem + }, + 'timeline/item/context': { + description: localize('view.timelineContext', "The Timeline view item context menu"), + type: 'array', + items: menuItem + }, } }; diff --git a/src/vs/workbench/contrib/timeline/browser/media/timelinePane.css b/src/vs/workbench/contrib/timeline/browser/media/timelinePane.css index ae431e80709b..ad6200c65617 100644 --- a/src/vs/workbench/contrib/timeline/browser/media/timelinePane.css +++ b/src/vs/workbench/contrib/timeline/browser/media/timelinePane.css @@ -13,3 +13,20 @@ position: absolute; pointer-events: none; } + +.timeline-tree-view .monaco-list .monaco-list-row .custom-view-tree-node-item .monaco-icon-label { + flex: 1; + text-overflow: ellipsis; + overflow: hidden; +} + +.timeline-tree-view .monaco-list .monaco-list-row .custom-view-tree-node-item .timeline-timestamp-container { + margin-left: 2px; + margin-right: 4px; + text-overflow: ellipsis; + overflow: hidden; +} + +.timeline-tree-view .monaco-list .monaco-list-row .custom-view-tree-node-item .timeline-timestamp-container .timeline-timestamp { + opacity: 0.5; +} diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index dfdc574d6a09..1afbedc65637 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -8,11 +8,11 @@ import { localize } from 'vs/nls'; import * as DOM from 'vs/base/browser/dom'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { IListVirtualDelegate, IIdentityProvider, IKeyboardNavigationLabelProvider } from 'vs/base/browser/ui/list/list'; -import { ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; +import { ITreeNode, ITreeRenderer, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { TreeResourceNavigator, WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -20,7 +20,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { TimelineItem, ITimelineService, TimelineChangeEvent, TimelineProvidersChangeEvent, TimelineRequest, TimelineItemWithSource } from 'vs/workbench/contrib/timeline/common/timeline'; +import { ITimelineService, TimelineChangeEvent, TimelineProvidersChangeEvent, TimelineRequest, TimelineItem } from 'vs/workbench/contrib/timeline/common/timeline'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { SideBySideEditor, toResource } from 'vs/workbench/common/editor'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -31,10 +31,20 @@ import { IProgressService } from 'vs/platform/progress/common/progress'; import { VIEWLET_ID } from 'vs/workbench/contrib/files/common/files'; import { debounce } from 'vs/base/common/decorators'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IActionViewItemProvider, ActionBar, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IAction, ActionRunner } from 'vs/base/common/actions'; +import { ContextAwareMenuEntryActionViewItem, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { MenuItemAction, IMenuService, MenuId } from 'vs/platform/actions/common/actions'; +import { fromNow } from 'vs/base/common/date'; + +// TODO[ECA]: Localize all the strings type TreeElement = TimelineItem; -// TODO[ECA]: Localize all the strings +interface TimelineActionContext { + uri: URI | undefined; + item: TreeElement; +} export class TimelinePane extends ViewPane { static readonly ID = 'timeline'; @@ -44,10 +54,12 @@ export class TimelinePane extends ViewPane { private _messageElement!: HTMLDivElement; private _treeElement!: HTMLDivElement; private _tree!: WorkbenchObjectTree; + private _treeRenderer: TimelineTreeRenderer | undefined; + private _menus: TimelineMenus; private _visibilityDisposables: DisposableStore | undefined; // private _excludedSources: Set | undefined; - private _items: TimelineItemWithSource[] = []; + private _items: TimelineItem[] = []; private _loadingMessageTimer: any | undefined; private _pendingRequests = new Map(); private _uri: URI | undefined; @@ -67,7 +79,9 @@ export class TimelinePane extends ViewPane { @IOpenerService openerService: IOpenerService, @IThemeService themeService: IThemeService, ) { - super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); + super({ ...options, titleMenuId: MenuId.TimelineTitle }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); + + this._menus = this._register(this.instantiationService.createInstance(TimelineMenus, this.id)); const scopedContextKeyService = this._register(this.contextKeyService.createScoped()); scopedContextKeyService.createKey('view', TimelinePane.ID); @@ -88,6 +102,7 @@ export class TimelinePane extends ViewPane { } this._uri = uri; + this._treeRenderer?.setUri(uri); this.loadTimeline(); } @@ -187,7 +202,7 @@ export class TimelinePane extends ViewPane { let request = this._pendingRequests.get(source); request?.tokenSource.dispose(true); - request = this.timelineService.getTimeline(source, this._uri, {}, new CancellationTokenSource())!; + request = this.timelineService.getTimeline(source, this._uri, {}, new CancellationTokenSource(), { cacheResults: true })!; this._pendingRequests.set(source, request); request.tokenSource.token.onCancellationRequested(() => this._pendingRequests.delete(source)); @@ -211,7 +226,7 @@ export class TimelinePane extends ViewPane { this.replaceItems(request.source, items); } - private replaceItems(source: string, items?: TimelineItemWithSource[]) { + private replaceItems(source: string, items?: TimelineItem[]) { const hasItems = this._items.length !== 0; if (items?.length) { @@ -291,17 +306,20 @@ export class TimelinePane extends ViewPane { // DOM.addClass(this._treeElement, 'show-file-icons'); container.appendChild(this._treeElement); - const renderer = this.instantiationService.createInstance(TimelineTreeRenderer); - this._tree = >this.instantiationService.createInstance(WorkbenchObjectTree, 'TimelinePane', this._treeElement, new TimelineListVirtualDelegate(), [renderer], { + this._treeRenderer = this.instantiationService.createInstance(TimelineTreeRenderer, this._menus); + this._tree = >this.instantiationService.createInstance(WorkbenchObjectTree, 'TimelinePane', + this._treeElement, new TimelineListVirtualDelegate(), [this._treeRenderer], { identityProvider: new TimelineIdentityProvider(), keyboardNavigationLabelProvider: new TimelineKeyboardNavigationLabelProvider(), overrideStyles: { - listBackground: this.getBackgroundColor() + listBackground: this.getBackgroundColor(), + } }); const customTreeNavigator = new TreeResourceNavigator(this._tree, { openOnFocus: false, openOnSelection: false }); this._register(customTreeNavigator); + this._register(this._tree.onContextMenu(e => this.onContextMenu(this._menus, e))); this._register( customTreeNavigator.onDidOpenResource(e => { if (!e.browserEvent) { @@ -316,36 +334,112 @@ export class TimelinePane extends ViewPane { }) ); } -} -export class TimelineElementTemplate { - static readonly id = 'TimelineElementTemplate'; + private onContextMenu(menus: TimelineMenus, treeEvent: ITreeContextMenuEvent): void { + const item = treeEvent.element; + if (item === null) { + return; + } + const event: UIEvent = treeEvent.browserEvent; - constructor( - readonly container: HTMLElement, - readonly iconLabel: IconLabel, - readonly icon: HTMLElement - ) { } -} + event.preventDefault(); + event.stopPropagation(); -export class TimelineIdentityProvider implements IIdentityProvider { - getId(item: TimelineItem): { toString(): string } { - return `${item.id}|${item.timestamp}`; + this._tree.setFocus([item]); + const actions = menus.getResourceContextActions(item); + if (!actions.length) { + return; + } + + this.contextMenuService.showContextMenu({ + getAnchor: () => treeEvent.anchor, + getActions: () => actions, + getActionViewItem: (action) => { + const keybinding = this.keybindingService.lookupKeybinding(action.id); + if (keybinding) { + return new ActionViewItem(action, action, { label: true, keybinding: keybinding.getLabel() }); + } + return undefined; + }, + onHide: (wasCancelled?: boolean) => { + if (wasCancelled) { + this._tree.domFocus(); + } + }, + getActionsContext: (): TimelineActionContext => ({ uri: this._uri, item: item }), + actionRunner: new TimelineActionRunner() + }); } } -export class TimelineKeyboardNavigationLabelProvider implements IKeyboardNavigationLabelProvider { - getKeyboardNavigationLabel(element: TimelineItem): { toString(): string } { +export class TimelineElementTemplate implements IDisposable { + static readonly id = 'TimelineElementTemplate'; + + readonly actionBar: ActionBar; + readonly icon: HTMLElement; + readonly iconLabel: IconLabel; + readonly timestamp: HTMLSpanElement; + + constructor( + readonly container: HTMLElement, + actionViewItemProvider: IActionViewItemProvider + ) { + DOM.addClass(container, 'custom-view-tree-node-item'); + this.icon = DOM.append(container, DOM.$('.custom-view-tree-node-item-icon')); + + this.iconLabel = new IconLabel(container, { supportHighlights: true, supportCodicons: true }); + + const timestampContainer = DOM.append(this.iconLabel.element, DOM.$('.timeline-timestamp-container')); + this.timestamp = DOM.append(timestampContainer, DOM.$('span.timeline-timestamp')); + + const actionsContainer = DOM.append(this.iconLabel.element, DOM.$('.actions')); + this.actionBar = new ActionBar(actionsContainer, { actionViewItemProvider: actionViewItemProvider }); + } + + dispose() { + this.iconLabel.dispose(); + this.actionBar.dispose(); + } + + reset() { + this.actionBar.clear(); + } +} + +export class TimelineIdentityProvider implements IIdentityProvider { + getId(item: TreeElement): { toString(): string } { + return item.handle; + } +} + +class TimelineActionRunner extends ActionRunner { + + runAction(action: IAction, { uri, item }: TimelineActionContext): Promise { + return action.run(...[ + { + $mid: 11, + handle: item.handle, + source: item.source, + uri: uri + }, + uri, + item.source, + ]); + } +} + +export class TimelineKeyboardNavigationLabelProvider implements IKeyboardNavigationLabelProvider { + getKeyboardNavigationLabel(element: TreeElement): { toString(): string } { return element.label; } } -export class TimelineListVirtualDelegate implements IListVirtualDelegate { - getHeight(_element: TimelineItem): number { +export class TimelineListVirtualDelegate implements IListVirtualDelegate { + getHeight(_element: TreeElement): number { return 22; } - getTemplateId(element: TimelineItem): string { + getTemplateId(element: TreeElement): string { return TimelineElementTemplate.id; } } @@ -353,14 +447,25 @@ export class TimelineListVirtualDelegate implements IListVirtualDelegate { readonly templateId: string = TimelineElementTemplate.id; - constructor(@IThemeService private _themeService: IThemeService) { } + private _actionViewItemProvider: IActionViewItemProvider; + + constructor( + private readonly _menus: TimelineMenus, + @IInstantiationService protected readonly instantiationService: IInstantiationService, + @IThemeService private _themeService: IThemeService + ) { + this._actionViewItemProvider = (action: IAction) => action instanceof MenuItemAction + ? this.instantiationService.createInstance(ContextAwareMenuEntryActionViewItem, action) + : undefined; + } + + private _uri: URI | undefined; + setUri(uri: URI | undefined) { + this._uri = uri; + } renderTemplate(container: HTMLElement): TimelineElementTemplate { - DOM.addClass(container, 'custom-view-tree-node-item'); - const icon = DOM.append(container, DOM.$('.custom-view-tree-node-item-icon')); - - const iconLabel = new IconLabel(container, { supportHighlights: true, supportCodicons: true }); - return new TimelineElementTemplate(container, iconLabel, icon); + return new TimelineElementTemplate(container, this._actionViewItemProvider); } renderElement( @@ -369,30 +474,74 @@ class TimelineTreeRenderer implements ITreeRenderer /^inline/.test(g)); + + menu.dispose(); + contextKeyService.dispose(); + + return result; + } +} diff --git a/src/vs/workbench/contrib/timeline/common/timeline.ts b/src/vs/workbench/contrib/timeline/common/timeline.ts index 4d3ece3caa75..7ffca6086461 100644 --- a/src/vs/workbench/contrib/timeline/common/timeline.ts +++ b/src/vs/workbench/contrib/timeline/common/timeline.ts @@ -16,9 +16,11 @@ export function toKey(extension: ExtensionIdentifier | string, source: string) { } export interface TimelineItem { + handle: string; + source: string; + timestamp: number; label: string; - id?: string; icon?: URI, iconDark?: URI, themeIcon?: { id: string }, @@ -28,10 +30,6 @@ export interface TimelineItem { contextValue?: string; } -export interface TimelineItemWithSource extends TimelineItem { - source: string; -} - export interface TimelineChangeEvent { id: string; uri?: URI; @@ -45,7 +43,7 @@ export interface TimelineCursor { export interface Timeline { source: string; - items: TimelineItemWithSource[]; + items: TimelineItem[]; cursor?: any; more?: boolean; @@ -54,7 +52,7 @@ export interface Timeline { export interface TimelineProvider extends TimelineProviderDescriptor, IDisposable { onDidChange?: Event; - provideTimeline(uri: URI, cursor: TimelineCursor, token: CancellationToken): Promise; + provideTimeline(uri: URI, cursor: TimelineCursor, token: CancellationToken, options?: { cacheResults?: boolean }): Promise; } export interface TimelineProviderDescriptor { @@ -86,7 +84,7 @@ export interface ITimelineService { getSources(): string[]; - getTimeline(id: string, uri: URI, pagination: TimelineCursor, tokenSource: CancellationTokenSource): TimelineRequest | undefined; + getTimeline(id: string, uri: URI, cursor: TimelineCursor, tokenSource: CancellationTokenSource, options?: { cacheResults?: boolean }): TimelineRequest | undefined; } const TIMELINE_SERVICE_ID = 'timeline'; diff --git a/src/vs/workbench/contrib/timeline/common/timelineService.ts b/src/vs/workbench/contrib/timeline/common/timelineService.ts index 3fc416e74647..270381062722 100644 --- a/src/vs/workbench/contrib/timeline/common/timelineService.ts +++ b/src/vs/workbench/contrib/timeline/common/timelineService.ts @@ -81,7 +81,7 @@ export class TimelineService implements ITimelineService { return [...this._providers.keys()]; } - getTimeline(id: string, uri: URI, cursor: TimelineCursor, tokenSource: CancellationTokenSource) { + getTimeline(id: string, uri: URI, cursor: TimelineCursor, tokenSource: CancellationTokenSource, options?: { cacheResults?: boolean }) { this.logService.trace(`TimelineService#getTimeline(${id}): uri=${uri.toString(true)}`); const provider = this._providers.get(id); @@ -98,7 +98,7 @@ export class TimelineService implements ITimelineService { } return { - result: provider.provideTimeline(uri, cursor, tokenSource.token) + result: provider.provideTimeline(uri, cursor, tokenSource.token, options) .then(result => { if (result === undefined) { return undefined; From 2f9c79a8470f42854c9ca81aa28762993fbbfb7a Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 14 Feb 2020 12:39:29 -0800 Subject: [PATCH 073/109] Only enable webview copy tests on MacOS --- .../src/singlefolder-tests/webview.test.ts | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts index df8af33f6b2c..3a7220ec34d2 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts @@ -3,11 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'mocha'; import * as assert from 'assert'; -import * as vscode from 'vscode'; +import 'mocha'; +import * as os from 'os'; import { join } from 'path'; -import { closeAllEditors, disposeAll, conditionalTest, delay } from '../utils'; +import * as vscode from 'vscode'; +import { closeAllEditors, conditionalTest, delay, disposeAll } from '../utils'; const webviewId = 'myWebview'; @@ -334,26 +335,28 @@ suite('Webview tests', () => { assert.strictEqual(webview.viewColumn, vscode.ViewColumn.One); }); - test('webview can copy text from webview', async () => { - const expectedText = `webview text from: ${Date.now()}!`; + if (os.platform() === 'darwin') { + test('webview can copy text from webview', async () => { + const expectedText = `webview text from: ${Date.now()}!`; - const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, { enableScripts: true, retainContextWhenHidden: true })); - const ready = getMesssage(webview); + const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, { enableScripts: true, retainContextWhenHidden: true })); + const ready = getMesssage(webview); - webview.webview.html = createHtmlDocumentWithBody(/*html*/` + webview.webview.html = createHtmlDocumentWithBody(/*html*/` ${expectedText} `); - await ready; + await ready; - await vscode.commands.executeCommand('editor.action.webvieweditor.copy'); - await delay(200); // Make sure copy has time to reach webview - assert.strictEqual(await vscode.env.clipboard.readText(), expectedText); - }); + await vscode.commands.executeCommand('editor.action.webvieweditor.copy'); + await delay(200); // Make sure copy has time to reach webview + assert.strictEqual(await vscode.env.clipboard.readText(), expectedText); + }); + } }); function createHtmlDocumentWithBody(body: string): string { From 10787693138ce600935e7163341a5767b4419fc9 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 14 Feb 2020 21:44:23 +0100 Subject: [PATCH 074/109] Tweaks for rendering the minimap with few lines and entire document --- src/vs/editor/common/config/editorOptions.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 0a9ae7435d6b..2ec1caef5072 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -1874,8 +1874,10 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption configuredFontScale) { + minimapWidthMultiplier = Math.min(2, minimapScale / configuredFontScale); + } minimapCharWidth = minimapScale / pixelRatio / minimapWidthMultiplier; minimapCanvasInnerHeight = Math.ceil((modelLineCount + extraLinesBeyondLastLine) * minimapLineHeight); } From 91fc43dbeeb7e06f2056f5209ef068f44ded5732 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 14 Feb 2020 13:02:14 -0800 Subject: [PATCH 075/109] Add workaround for preferences editor strict function error For #81574 --- src/vs/platform/instantiation/common/instantiation.ts | 2 +- .../contrib/preferences/browser/preferencesEditor.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/platform/instantiation/common/instantiation.ts b/src/vs/platform/instantiation/common/instantiation.ts index 10ff5a25051d..7e130cc39632 100644 --- a/src/vs/platform/instantiation/common/instantiation.ts +++ b/src/vs/platform/instantiation/common/instantiation.ts @@ -29,7 +29,7 @@ export interface IConstructorSignature0 { } export interface IConstructorSignature1 { - new(first: A1, ...services: BrandedService[]): T; + new (first: A1, ...services: Services): T; } export interface IConstructorSignature2 { diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts index 60bcbfd4ba48..a8865d6ec602 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts @@ -29,7 +29,7 @@ import { SelectionHighlighter } from 'vs/editor/contrib/multicursor/multicursor' import * as nls from 'vs/nls'; import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, IConstructorSignature1 } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -984,7 +984,7 @@ export class DefaultPreferencesEditor extends BaseTextEditor { private static _getContributions(): IEditorContributionDescription[] { const skipContributions = [FoldingController.ID, SelectionHighlighter.ID, FindController.ID]; const contributions = EditorExtensionsRegistry.getEditorContributions().filter(c => skipContributions.indexOf(c.id) === -1); - contributions.push({ id: DefaultSettingsEditorContribution.ID, ctor: DefaultSettingsEditorContribution }); + contributions.push({ id: DefaultSettingsEditorContribution.ID, ctor: DefaultSettingsEditorContribution as IConstructorSignature1 }); return contributions; } From 5c017be321106b790c0a6ebd709e1bd9db5ec94a Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Fri, 14 Feb 2020 18:55:11 -0500 Subject: [PATCH 076/109] Fixes broken test with git extension --- extensions/git/package.json | 3 +- extensions/git/yarn.lock | 72 ++++++++++++++++++++++++++++++++++++- 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index b1ae36f57a7f..9d6d4f43f3d1 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1833,6 +1833,7 @@ "@types/which": "^1.0.28", "mocha": "^3.2.0", "mocha-junit-reporter": "^1.23.3", - "mocha-multi-reporters": "^1.1.7" + "mocha-multi-reporters": "^1.1.7", + "vscode-test": "^1.3.0" } } diff --git a/extensions/git/yarn.lock b/extensions/git/yarn.lock index ff2f89944877..0ae8237610cf 100644 --- a/extensions/git/yarn.lock +++ b/extensions/git/yarn.lock @@ -41,6 +41,13 @@ resolved "https://registry.yarnpkg.com/@types/which/-/which-1.0.28.tgz#016e387629b8817bed653fe32eab5d11279c8df6" integrity sha1-AW44dim4gXvtZT/jLqtdESecjfY= +agent-base@4, agent-base@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" + integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg== + dependencies: + es6-promisify "^5.0.0" + ansi-regex@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" @@ -112,6 +119,13 @@ debug@2.6.8: dependencies: ms "2.0.0" +debug@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== + dependencies: + ms "2.0.0" + debug@^2.2.0: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -143,6 +157,18 @@ diff@3.2.0: resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9" integrity sha1-yc45Okt8vQsFinJck98pkCeGj/k= +es6-promise@^4.0.3: + version "4.2.8" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" + integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== + +es6-promisify@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" + integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= + dependencies: + es6-promise "^4.0.3" + escape-string-regexp@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" @@ -170,6 +196,18 @@ glob@7.1.1: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^7.1.3: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + "graceful-readlink@>= 1.0.0": version "1.0.1" resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" @@ -190,6 +228,22 @@ he@1.1.1: resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0= +http-proxy-agent@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405" + integrity sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg== + dependencies: + agent-base "4" + debug "3.1.0" + +https-proxy-agent@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b" + integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg== + dependencies: + agent-base "^4.3.0" + debug "^3.1.0" + iconv-lite@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -300,7 +354,7 @@ md5@^2.1.0: crypt "~0.0.1" is-buffer "~1.1.1" -minimatch@^3.0.2: +minimatch@^3.0.2, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== @@ -378,6 +432,13 @@ path-is-absolute@^1.0.0: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= +rimraf@^2.6.3: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + "safer-buffer@>= 2.1.2 < 3": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -414,6 +475,15 @@ vscode-nls@^4.0.0: resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.0.0.tgz#4001c8a6caba5cedb23a9c5ce1090395c0e44002" integrity sha512-qCfdzcH+0LgQnBpZA53bA32kzp9rpq/f66Som577ObeuDlFIrtbEJ+A/+CCxjIh4G8dpJYNCKIsxpRAHIfsbNw== +vscode-test@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/vscode-test/-/vscode-test-1.3.0.tgz#3310ab385d9b887b4c82e8f52be1030e7cf9493d" + integrity sha512-LddukcBiSU2FVTDr3c1D8lwkiOvwlJdDL2hqVbn6gIz+rpTqUCkMZSKYm94Y1v0WXlHSDQBsXyY+tchWQgGVsw== + dependencies: + http-proxy-agent "^2.1.0" + https-proxy-agent "^2.2.4" + rimraf "^2.6.3" + vscode-uri@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-2.0.0.tgz#2df704222f72b8a71ff266ba0830ed6c51ac1542" From 8503705b114c93eb54840f23954d830264b7bbad Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 14 Feb 2020 16:57:25 -0800 Subject: [PATCH 077/109] Don't show reference code lens for both class and ctor in es5 classes Fixes #90396 --- .../src/features/referencesCodeLens.ts | 8 ++++++++ .../src/test/referencesCodeLens.test.ts | 13 +++++++++++++ 2 files changed, 21 insertions(+) diff --git a/extensions/typescript-language-features/src/features/referencesCodeLens.ts b/extensions/typescript-language-features/src/features/referencesCodeLens.ts index 5e92ce06e01a..7aa228f8f8e4 100644 --- a/extensions/typescript-language-features/src/features/referencesCodeLens.ts +++ b/extensions/typescript-language-features/src/features/referencesCodeLens.ts @@ -99,6 +99,14 @@ export class TypeScriptReferencesCodeLensProvider extends TypeScriptBaseCodeLens case PConst.Kind.memberSetAccessor: case PConst.Kind.constructorImplementation: case PConst.Kind.memberVariable: + // Don't show if child and parent have same start + // For https://github.com/microsoft/vscode/issues/90396 + if (parent && + typeConverters.Position.fromLocation(parent.spans[0].start).isEqual(typeConverters.Position.fromLocation(item.spans[0].start)) + ) { + return null; + } + // Only show if parent is a class type object (not a literal) switch (parent?.kind) { case PConst.Kind.class: diff --git a/extensions/typescript-language-features/src/test/referencesCodeLens.test.ts b/extensions/typescript-language-features/src/test/referencesCodeLens.test.ts index 7ca35ed89ccb..89689d57ae68 100644 --- a/extensions/typescript-language-features/src/test/referencesCodeLens.test.ts +++ b/extensions/typescript-language-features/src/test/referencesCodeLens.test.ts @@ -91,6 +91,19 @@ suite('TypeScript References', () => { const codeLenses = await getCodeLenses(testDocumentUri); assert.strictEqual(codeLenses?.length, 0); }); + + test('Should not show duplicate references on ES5 class (https://github.com/microsoft/vscode/issues/90396)', async () => { + const testDocumentUri = vscode.Uri.parse('untitled:test3.js'); + await createTestEditor(testDocumentUri, + `function A() {`, + ` console.log("hi");`, + `}`, + `A.x = {};`, + ); + + const codeLenses = await getCodeLenses(testDocumentUri); + assert.strictEqual(codeLenses?.length, 1); + }); }); function getCodeLenses(document: vscode.Uri): Thenable { From 672bc17e2260619b25b09e9479478ddcf4b35264 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 14 Feb 2020 16:57:35 -0800 Subject: [PATCH 078/109] Remove extra cast --- src/vs/workbench/api/common/extHostTypeConverters.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index c998bd2c5f88..6027377e166d 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -257,7 +257,7 @@ export namespace MarkdownString { } else if (htmlContent.isMarkdownString(markup)) { res = markup; } else if (typeof markup === 'string') { - res = { value: markup }; + res = { value: markup }; } else { res = { value: '' }; } From 1a81711a85e38ccf784110568ebf3784ab9094a5 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 14 Feb 2020 17:10:28 -0800 Subject: [PATCH 079/109] Add reopen with to editor title menu Fixes #89604 --- .../contrib/customEditor/browser/commands.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/customEditor/browser/commands.ts b/src/vs/workbench/contrib/customEditor/browser/commands.ts index 6814383e70b8..71c474a0b15d 100644 --- a/src/vs/workbench/contrib/customEditor/browser/commands.ts +++ b/src/vs/workbench/contrib/customEditor/browser/commands.ts @@ -40,7 +40,7 @@ CommandsRegistry.registerCommand('_workbench.openWith', (accessor: ServicesAcces // #region Reopen With const REOPEN_WITH_COMMAND_ID = 'reOpenWith'; -const REOPEN_WITH_TITLE = { value: nls.localize('reopenWith.title', 'Reopen With'), original: 'Reopen With' }; +const REOPEN_WITH_TITLE = { value: nls.localize('reopenWith.title', 'Reopen With...'), original: 'Reopen With' }; KeybindingsRegistry.registerCommandAndKeybindingRule({ id: REOPEN_WITH_COMMAND_ID, @@ -83,6 +83,17 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { when: CONTEXT_HAS_CUSTOM_EDITORS, }); +MenuRegistry.appendMenuItem(MenuId.EditorTitle, { + command: { + id: REOPEN_WITH_COMMAND_ID, + title: REOPEN_WITH_TITLE, + category: viewCategory, + }, + group: '3_open', + order: 20, + when: CONTEXT_HAS_CUSTOM_EDITORS, +}); + // #endregion From feecdaeacf4e354a7a1e8ac264a84132e1d03548 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Sat, 15 Feb 2020 12:49:11 -0800 Subject: [PATCH 080/109] Disable webview copy test on web --- .../vscode-api-tests/src/singlefolder-tests/webview.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts index 3a7220ec34d2..06d284c37622 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts @@ -336,7 +336,7 @@ suite('Webview tests', () => { }); if (os.platform() === 'darwin') { - test('webview can copy text from webview', async () => { + conditionalTest('webview can copy text from webview', async () => { const expectedText = `webview text from: ${Date.now()}!`; const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, { enableScripts: true, retainContextWhenHidden: true })); From 6ec6f9e3f4d17f0cef0480688a1f350b9141e567 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Sat, 15 Feb 2020 13:13:22 -0800 Subject: [PATCH 081/109] Fix git integration tests Move back to using normal vscode dependency for now . We will look into moving off it later --- extensions/git/package.json | 3 +- extensions/git/yarn.lock | 457 ++++++++++++++++++++++++++++++++++-- 2 files changed, 437 insertions(+), 23 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 9d6d4f43f3d1..044c37c18b4c 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1829,11 +1829,10 @@ "@types/file-type": "^5.2.1", "@types/mocha": "2.2.43", "@types/node": "^12.11.7", - "@types/vscode": "^1.42", "@types/which": "^1.0.28", "mocha": "^3.2.0", "mocha-junit-reporter": "^1.23.3", "mocha-multi-reporters": "^1.1.7", - "vscode-test": "^1.3.0" + "vscode": "^1.1.36" } } diff --git a/extensions/git/yarn.lock b/extensions/git/yarn.lock index 0ae8237610cf..ac135e43a804 100644 --- a/extensions/git/yarn.lock +++ b/extensions/git/yarn.lock @@ -31,11 +31,6 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-12.11.7.tgz#57682a9771a3f7b09c2497f28129a0462966524a" integrity sha512-JNbGaHFCLwgHn/iCckiGSOZ1XYHsKFwREtzPwSGCVld1SGhOlmZw2D4ZI94HQCrBHbADzW9m4LER/8olJTRGHA== -"@types/vscode@^1.42": - version "1.42.0" - resolved "https://registry.yarnpkg.com/@types/vscode/-/vscode-1.42.0.tgz#0ad891a9487e91e34be7c56985058a179031eb76" - integrity sha512-ds6TceMsh77Fs0Mq0Vap6Y72JbGWB8Bay4DrnJlf5d9ui2RSe1wis13oQm+XhguOeH1HUfLGzaDAoupTUtgabw== - "@types/which@^1.0.28": version "1.0.28" resolved "https://registry.yarnpkg.com/@types/which/-/which-1.0.28.tgz#016e387629b8817bed653fe32eab5d11279c8df6" @@ -48,6 +43,16 @@ agent-base@4, agent-base@^4.3.0: dependencies: es6-promisify "^5.0.0" +ajv@^6.5.5: + version "6.11.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.11.0.tgz#c3607cbc8ae392d8a5a536f25b21f8e5f3f87fe9" + integrity sha512-nCprB/0syFYy9fVYU1ox1l2KN8S9I+tziH8D4zdZuLT3N6RMlGSGt5FSTpAiHB/Whv8Qs1cWHma1aMKZyaHRKA== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + ansi-regex@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" @@ -62,11 +67,45 @@ applicationinsights@1.0.8: diagnostic-channel-publishers "0.2.1" zone.js "0.7.6" +asn1@~0.2.3: + version "0.2.4" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" + integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== + dependencies: + safer-buffer "~2.1.0" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= + +aws4@^1.8.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e" + integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug== + balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= + dependencies: + tweetnacl "^0.14.3" + brace-expansion@^1.1.7: version "1.1.8" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" @@ -80,16 +119,43 @@ browser-stdout@1.3.0: resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f" integrity sha1-81HTKWnTL6XXpVZxVCY9korjvR8= +browser-stdout@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" + integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== + +buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + byline@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1" integrity sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE= +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= + charenc@~0.0.1: version "0.0.2" resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc= +combined-stream@^1.0.6, combined-stream@~1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +commander@2.15.1: + version "2.15.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" + integrity sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag== + commander@2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" @@ -102,11 +168,23 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= +core-util-is@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + crypt@~0.0.1: version "0.0.2" resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs= +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= + dependencies: + assert-plus "^1.0.0" + dayjs@1.8.19: version "1.8.19" resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.8.19.tgz#5117dc390d8f8e586d53891dbff3fa308f51abfe" @@ -140,6 +218,11 @@ debug@^3.1.0: dependencies: ms "^2.1.1" +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + diagnostic-channel-publishers@0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz#8e2d607a8b6d79fe880b548bc58cc6beb288c4f3" @@ -157,6 +240,19 @@ diff@3.2.0: resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9" integrity sha1-yc45Okt8vQsFinJck98pkCeGj/k= +diff@3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" + integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== + +ecc-jsbn@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.1.0" + es6-promise@^4.0.3: version "4.2.8" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" @@ -174,16 +270,62 @@ escape-string-regexp@1.0.5: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= +extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= + +extsprintf@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" + integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= + +fast-deep-equal@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4" + integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + file-type@^7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/file-type/-/file-type-7.2.0.tgz#113cfed52e1d6959ab80248906e2f25a8cdccb74" integrity sha1-ETz+1S4daVmrgCSJBuLyWozcy3Q= +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= + +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= + dependencies: + assert-plus "^1.0.0" + glob@7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" @@ -196,7 +338,19 @@ glob@7.1.1: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.1.3: +glob@7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" + integrity sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^7.1.2: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== @@ -213,16 +367,39 @@ glob@^7.1.3: resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" integrity sha1-TK+tdrxi8C+gObL5Tpo906ORpyU= +growl@1.10.5: + version "1.10.5" + resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" + integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== + growl@1.9.2: version "1.9.2" resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f" integrity sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8= +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= + +har-validator@~5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" + integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== + dependencies: + ajv "^6.5.5" + har-schema "^2.0.0" + has-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" integrity sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo= +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + he@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" @@ -236,7 +413,16 @@ http-proxy-agent@^2.1.0: agent-base "4" debug "3.1.0" -https-proxy-agent@^2.2.4: +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +https-proxy-agent@^2.2.1: version "2.2.4" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b" integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg== @@ -269,21 +455,61 @@ is-buffer@~1.1.1: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= + jschardet@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-2.1.1.tgz#af6f8fd0b3b0f5d46a8fd9614a4fce490575c184" integrity sha512-pA5qG9Zwm8CBpGlK/lo2GE9jPxwqRgMV7Lzc/1iaPccw6v4Rhj8Zg2BTyrdmHmxlJojnbLupLeRnaPLsq03x6Q== +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= + +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= + json3@3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1" integrity sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE= +jsprim@^1.2.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" + integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.2.3" + verror "1.10.0" + lodash._baseassign@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz#8c38a099500f215ad09e59f1722fd0c52bfe0a4e" @@ -354,7 +580,19 @@ md5@^2.1.0: crypt "~0.0.1" is-buffer "~1.1.1" -minimatch@^3.0.2, minimatch@^3.0.4: +mime-db@1.43.0: + version "1.43.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58" + integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ== + +mime-types@^2.1.12, mime-types@~2.1.19: + version "2.1.26" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06" + integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ== + dependencies: + mime-db "1.43.0" + +minimatch@3.0.4, minimatch@^3.0.2, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== @@ -410,6 +648,23 @@ mocha@^3.2.0: mkdirp "0.5.1" supports-color "3.1.2" +mocha@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-5.2.0.tgz#6d8ae508f59167f940f2b5b3c4a612ae50c90ae6" + integrity sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ== + dependencies: + browser-stdout "1.3.1" + commander "2.15.1" + debug "3.1.0" + diff "3.5.0" + escape-string-regexp "1.0.5" + glob "7.1.2" + growl "1.10.5" + he "1.1.1" + minimatch "3.0.4" + mkdirp "0.5.1" + supports-color "5.4.0" + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -420,6 +675,11 @@ ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== + once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -432,14 +692,68 @@ path-is-absolute@^1.0.0: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= -rimraf@^2.6.3: - version "2.7.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" - integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== - dependencies: - glob "^7.1.3" +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -"safer-buffer@>= 2.1.2 < 3": +psl@^1.1.28: + version "1.7.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.7.0.tgz#f1c4c47a8ef97167dea5d6bbf4816d736e884a3c" + integrity sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ== + +punycode@^2.1.0, punycode@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +qs@~6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== + +querystringify@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e" + integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA== + +request@^2.88.0: + version "2.88.2" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" + integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.5.0" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= + +safe-buffer@^5.0.1, safe-buffer@^5.1.2: + version "5.2.0" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" + integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== + +"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -449,6 +763,39 @@ semver@^5.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" integrity sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA== +semver@^5.4.1: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +source-map-support@^0.5.0: + version "0.5.16" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042" + integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +sshpk@^1.7.0: + version "1.16.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" + integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + strip-ansi@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" @@ -463,6 +810,62 @@ supports-color@3.1.2: dependencies: has-flag "^1.0.0" +supports-color@5.4.0: + version "5.4.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54" + integrity sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w== + dependencies: + has-flag "^3.0.0" + +tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= + +uri-js@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" + integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== + dependencies: + punycode "^2.1.0" + +url-parse@^1.4.4: + version "1.4.7" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278" + integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg== + dependencies: + querystringify "^2.1.1" + requires-port "^1.0.0" + +uuid@^3.3.2: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + vscode-extension-telemetry@0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.1.1.tgz#91387e06b33400c57abd48979b0e790415ae110b" @@ -475,20 +878,32 @@ vscode-nls@^4.0.0: resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.0.0.tgz#4001c8a6caba5cedb23a9c5ce1090395c0e44002" integrity sha512-qCfdzcH+0LgQnBpZA53bA32kzp9rpq/f66Som577ObeuDlFIrtbEJ+A/+CCxjIh4G8dpJYNCKIsxpRAHIfsbNw== -vscode-test@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/vscode-test/-/vscode-test-1.3.0.tgz#3310ab385d9b887b4c82e8f52be1030e7cf9493d" - integrity sha512-LddukcBiSU2FVTDr3c1D8lwkiOvwlJdDL2hqVbn6gIz+rpTqUCkMZSKYm94Y1v0WXlHSDQBsXyY+tchWQgGVsw== +vscode-test@^0.4.1: + version "0.4.3" + resolved "https://registry.yarnpkg.com/vscode-test/-/vscode-test-0.4.3.tgz#461ebf25fc4bc93d77d982aed556658a2e2b90b8" + integrity sha512-EkMGqBSefZH2MgW65nY05rdRSko15uvzq4VAPM5jVmwYuFQKE7eikKXNJDRxL+OITXHB6pI+a3XqqD32Y3KC5w== dependencies: http-proxy-agent "^2.1.0" - https-proxy-agent "^2.2.4" - rimraf "^2.6.3" + https-proxy-agent "^2.2.1" vscode-uri@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-2.0.0.tgz#2df704222f72b8a71ff266ba0830ed6c51ac1542" integrity sha512-lWXWofDSYD8r/TIyu64MdwB4FaSirQ608PP/TzUyslyOeHGwQ0eTHUZeJrK1ILOmwUHaJtV693m2JoUYroUDpw== +vscode@^1.1.36: + version "1.1.36" + resolved "https://registry.yarnpkg.com/vscode/-/vscode-1.1.36.tgz#5e1a0d1bf4977d0c7bc5159a9a13d5b104d4b1b6" + integrity sha512-cGFh9jmGLcTapCpPCKvn8aG/j9zVQ+0x5hzYJq5h5YyUXVGa1iamOaB2M2PZXoumQPES4qeAP1FwkI0b6tL4bQ== + dependencies: + glob "^7.1.2" + mocha "^5.2.0" + request "^2.88.0" + semver "^5.4.1" + source-map-support "^0.5.0" + url-parse "^1.4.4" + vscode-test "^0.4.1" + which@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" From d8e7eb36a2286a9f7ef37771493e963284fa2d0a Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 17 Feb 2020 10:42:31 +0100 Subject: [PATCH 082/109] Text save participants are overwritten for each extension host (fixes #90359) --- .../src/singlefolder-tests/workspace.test.ts | 7 - .../api/browser/mainThreadSaveParticipant.ts | 349 +----------------- .../browser/codeEditor.contribution.ts | 1 + .../codeEditor/browser/saveParticipants.ts | 316 ++++++++++++++++ .../test/browser/saveParticipant.test.ts} | 4 +- .../textfile/browser/textFileService.ts | 4 +- .../textfile/common/textFileEditorModel.ts | 22 +- .../common/textFileEditorModelManager.ts | 19 +- .../common/textFileSaveParticipant.ts | 69 ++++ .../services/textfile/common/textfiles.ts | 31 +- .../test/browser/textFileEditorModel.test.ts | 86 ++++- .../test/browser/workbenchTestServices.ts | 15 + 12 files changed, 535 insertions(+), 388 deletions(-) create mode 100644 src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts rename src/vs/workbench/{test/electron-browser/api/mainThreadSaveParticipant.test.ts => contrib/codeEditor/test/browser/saveParticipant.test.ts} (98%) create mode 100644 src/vs/workbench/services/textfile/common/textFileSaveParticipant.ts diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts index 3e56f9271680..c26c88671ddf 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts @@ -215,13 +215,6 @@ suite('workspace-namespace', () => { }); test('eol, change via onWillSave', async function () { - if (vscode.env.uiKind === vscode.UIKind.Web) { - // TODO@Jo Test seems to fail when running in web due to - // onWillSaveTextDocument not getting called - this.skip(); - return; - } - let called = false; let sub = vscode.workspace.onWillSaveTextDocument(e => { called = true; diff --git a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts index 288a349b183b..ce7aa7a7b536 100644 --- a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts +++ b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts @@ -3,304 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IdleValue, raceCancellation } from 'vs/base/common/async'; -import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation'; -import * as strings from 'vs/base/common/strings'; -import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; -import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { trimTrailingWhitespace } from 'vs/editor/common/commands/trimTrailingWhitespaceCommand'; -import { EditOperation } from 'vs/editor/common/core/editOperation'; -import { Position } from 'vs/editor/common/core/position'; -import { Range } from 'vs/editor/common/core/range'; -import { Selection } from 'vs/editor/common/core/selection'; -import { ITextModel } from 'vs/editor/common/model'; -import { CodeAction, CodeActionTriggerType } from 'vs/editor/common/modes'; +import { CancellationToken } from 'vs/base/common/cancellation'; import { shouldSynchronizeModel } from 'vs/editor/common/services/modelService'; -import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction'; -import { applyCodeAction } from 'vs/editor/contrib/codeAction/codeActionCommands'; -import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; -import { formatDocumentWithSelectedProvider, FormattingMode } from 'vs/editor/contrib/format/format'; -import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2'; import { localize } from 'vs/nls'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ILogService } from 'vs/platform/log/common/log'; -import { IProgressService, ProgressLocation, IProgressStep, IProgress } from 'vs/platform/progress/common/progress'; +import { IProgressStep, IProgress } from 'vs/platform/progress/common/progress'; import { extHostCustomer } from 'vs/workbench/api/common/extHostCustomers'; -import { ISaveParticipant, IResolvedTextFileEditorModel, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileSaveParticipant, IResolvedTextFileEditorModel, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { SaveReason } from 'vs/workbench/common/editor'; import { ExtHostContext, ExtHostDocumentSaveParticipantShape, IExtHostContext } from '../common/extHost.protocol'; -import { ILabelService } from 'vs/platform/label/common/label'; import { canceled } from 'vs/base/common/errors'; +import { IDisposable } from 'vs/base/common/lifecycle'; -export interface ISaveParticipantParticipant { - participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason }, progress: IProgress, token: CancellationToken): Promise; -} - -class TrimWhitespaceParticipant implements ISaveParticipantParticipant { - - constructor( - @IConfigurationService private readonly configurationService: IConfigurationService, - @ICodeEditorService private readonly codeEditorService: ICodeEditorService - ) { - // Nothing - } - - async participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason; }): Promise { - if (this.configurationService.getValue('files.trimTrailingWhitespace', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.resource })) { - this.doTrimTrailingWhitespace(model.textEditorModel, env.reason === SaveReason.AUTO); - } - } - - private doTrimTrailingWhitespace(model: ITextModel, isAutoSaved: boolean): void { - let prevSelection: Selection[] = []; - let cursors: Position[] = []; - - const editor = findEditor(model, this.codeEditorService); - if (editor) { - // Find `prevSelection` in any case do ensure a good undo stack when pushing the edit - // Collect active cursors in `cursors` only if `isAutoSaved` to avoid having the cursors jump - prevSelection = editor.getSelections(); - if (isAutoSaved) { - cursors = prevSelection.map(s => s.getPosition()); - const snippetsRange = SnippetController2.get(editor).getSessionEnclosingRange(); - if (snippetsRange) { - for (let lineNumber = snippetsRange.startLineNumber; lineNumber <= snippetsRange.endLineNumber; lineNumber++) { - cursors.push(new Position(lineNumber, model.getLineMaxColumn(lineNumber))); - } - } - } - } - - const ops = trimTrailingWhitespace(model, cursors); - if (!ops.length) { - return; // Nothing to do - } - - model.pushEditOperations(prevSelection, ops, (_edits) => prevSelection); - } -} - -function findEditor(model: ITextModel, codeEditorService: ICodeEditorService): IActiveCodeEditor | null { - let candidate: IActiveCodeEditor | null = null; - - if (model.isAttachedToEditor()) { - for (const editor of codeEditorService.listCodeEditors()) { - if (editor.hasModel() && editor.getModel() === model) { - if (editor.hasTextFocus()) { - return editor; // favour focused editor if there are multiple - } - - candidate = editor; - } - } - } - - return candidate; -} - -export class FinalNewLineParticipant implements ISaveParticipantParticipant { - - constructor( - @IConfigurationService private readonly configurationService: IConfigurationService, - @ICodeEditorService private readonly codeEditorService: ICodeEditorService - ) { - // Nothing - } - - async participate(model: IResolvedTextFileEditorModel, _env: { reason: SaveReason; }): Promise { - if (this.configurationService.getValue('files.insertFinalNewline', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.resource })) { - this.doInsertFinalNewLine(model.textEditorModel); - } - } - - private doInsertFinalNewLine(model: ITextModel): void { - const lineCount = model.getLineCount(); - const lastLine = model.getLineContent(lineCount); - const lastLineIsEmptyOrWhitespace = strings.lastNonWhitespaceIndex(lastLine) === -1; - - if (!lineCount || lastLineIsEmptyOrWhitespace) { - return; - } - - const edits = [EditOperation.insert(new Position(lineCount, model.getLineMaxColumn(lineCount)), model.getEOL())]; - const editor = findEditor(model, this.codeEditorService); - if (editor) { - editor.executeEdits('insertFinalNewLine', edits, editor.getSelections()); - } else { - model.pushEditOperations([], edits, () => null); - } - } -} - -export class TrimFinalNewLinesParticipant implements ISaveParticipantParticipant { - - constructor( - @IConfigurationService private readonly configurationService: IConfigurationService, - @ICodeEditorService private readonly codeEditorService: ICodeEditorService - ) { - // Nothing - } - - async participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason; }): Promise { - if (this.configurationService.getValue('files.trimFinalNewlines', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.resource })) { - this.doTrimFinalNewLines(model.textEditorModel, env.reason === SaveReason.AUTO); - } - } - - /** - * returns 0 if the entire file is empty or whitespace only - */ - private findLastLineWithContent(model: ITextModel): number { - for (let lineNumber = model.getLineCount(); lineNumber >= 1; lineNumber--) { - const lineContent = model.getLineContent(lineNumber); - if (strings.lastNonWhitespaceIndex(lineContent) !== -1) { - // this line has content - return lineNumber; - } - } - // no line has content - return 0; - } - - private doTrimFinalNewLines(model: ITextModel, isAutoSaved: boolean): void { - const lineCount = model.getLineCount(); - - // Do not insert new line if file does not end with new line - if (lineCount === 1) { - return; - } - - let prevSelection: Selection[] = []; - let cannotTouchLineNumber = 0; - const editor = findEditor(model, this.codeEditorService); - if (editor) { - prevSelection = editor.getSelections(); - if (isAutoSaved) { - for (let i = 0, len = prevSelection.length; i < len; i++) { - const positionLineNumber = prevSelection[i].positionLineNumber; - if (positionLineNumber > cannotTouchLineNumber) { - cannotTouchLineNumber = positionLineNumber; - } - } - } - } - - const lastLineNumberWithContent = this.findLastLineWithContent(model); - const deleteFromLineNumber = Math.max(lastLineNumberWithContent + 1, cannotTouchLineNumber + 1); - const deletionRange = model.validateRange(new Range(deleteFromLineNumber, 1, lineCount, model.getLineMaxColumn(lineCount))); - - if (deletionRange.isEmpty()) { - return; - } - - model.pushEditOperations(prevSelection, [EditOperation.delete(deletionRange)], _edits => prevSelection); - - if (editor) { - editor.setSelections(prevSelection); - } - } -} - -class FormatOnSaveParticipant implements ISaveParticipantParticipant { - - constructor( - @IConfigurationService private readonly _configurationService: IConfigurationService, - @ICodeEditorService private readonly _codeEditorService: ICodeEditorService, - @IInstantiationService private readonly _instantiationService: IInstantiationService, - ) { - // Nothing - } - - async participate(editorModel: IResolvedTextFileEditorModel, env: { reason: SaveReason; }, progress: IProgress, token: CancellationToken): Promise { - - const model = editorModel.textEditorModel; - const overrides = { overrideIdentifier: model.getLanguageIdentifier().language, resource: model.uri }; - - if (env.reason === SaveReason.AUTO || !this._configurationService.getValue('editor.formatOnSave', overrides)) { - return undefined; - } - - progress.report({ message: localize('formatting', "Formatting") }); - const editorOrModel = findEditor(model, this._codeEditorService) || model; - await this._instantiationService.invokeFunction(formatDocumentWithSelectedProvider, editorOrModel, FormattingMode.Silent, token); - } -} - -class CodeActionOnSaveParticipant implements ISaveParticipantParticipant { - - constructor( - @IConfigurationService private readonly _configurationService: IConfigurationService, - @IInstantiationService private readonly _instantiationService: IInstantiationService, - ) { } - - async participate(editorModel: IResolvedTextFileEditorModel, env: { reason: SaveReason; }, progress: IProgress, token: CancellationToken): Promise { - if (env.reason === SaveReason.AUTO) { - return undefined; - } - const model = editorModel.textEditorModel; - - const settingsOverrides = { overrideIdentifier: model.getLanguageIdentifier().language, resource: editorModel.resource }; - const setting = this._configurationService.getValue<{ [kind: string]: boolean }>('editor.codeActionsOnSave', settingsOverrides); - if (!setting) { - return undefined; - } - - const codeActionsOnSave = Object.keys(setting) - .filter(x => setting[x]).map(x => new CodeActionKind(x)) - .sort((a, b) => { - if (CodeActionKind.SourceFixAll.contains(a)) { - if (CodeActionKind.SourceFixAll.contains(b)) { - return 0; - } - return -1; - } - if (CodeActionKind.SourceFixAll.contains(b)) { - return 1; - } - return 0; - }); - - if (!codeActionsOnSave.length) { - return undefined; - } - - const excludedActions = Object.keys(setting) - .filter(x => setting[x] === false) - .map(x => new CodeActionKind(x)); - - progress.report({ message: localize('codeaction', "Quick Fixes") }); - await this.applyOnSaveActions(model, codeActionsOnSave, excludedActions, token); - } - - private async applyOnSaveActions(model: ITextModel, codeActionsOnSave: readonly CodeActionKind[], excludes: readonly CodeActionKind[], token: CancellationToken): Promise { - for (const codeActionKind of codeActionsOnSave) { - const actionsToRun = await this.getActionsToRun(model, codeActionKind, excludes, token); - try { - await this.applyCodeActions(actionsToRun.validActions); - } catch { - // Failure to apply a code action should not block other on save actions - } finally { - actionsToRun.dispose(); - } - } - } - - private async applyCodeActions(actionsToRun: readonly CodeAction[]) { - for (const action of actionsToRun) { - await this._instantiationService.invokeFunction(applyCodeAction, action); - } - } - - private getActionsToRun(model: ITextModel, codeActionKind: CodeActionKind, excludes: readonly CodeActionKind[], token: CancellationToken) { - return getCodeActions(model, model.getFullModelRange(), { - type: CodeActionTriggerType.Auto, - filter: { include: codeActionKind, excludes: excludes, includeSourceActions: true }, - }, token); - } -} - -class ExtHostSaveParticipant implements ISaveParticipantParticipant { +class ExtHostSaveParticipant implements ITextFileSaveParticipant { private readonly _proxy: ExtHostDocumentSaveParticipantShape; @@ -336,65 +51,19 @@ class ExtHostSaveParticipant implements ISaveParticipantParticipant { // The save participant can change a model before its saved to support various scenarios like trimming trailing whitespace @extHostCustomer -export class SaveParticipant implements ISaveParticipant { +export class SaveParticipant { - private readonly _saveParticipants: IdleValue; + private _saveParticipantDisposable: IDisposable; constructor( extHostContext: IExtHostContext, @IInstantiationService instantiationService: IInstantiationService, - @IProgressService private readonly _progressService: IProgressService, - @ILogService private readonly _logService: ILogService, - @ILabelService private readonly _labelService: ILabelService, @ITextFileService private readonly _textFileService: ITextFileService ) { - this._saveParticipants = new IdleValue(() => [ - instantiationService.createInstance(TrimWhitespaceParticipant), - instantiationService.createInstance(CodeActionOnSaveParticipant), - instantiationService.createInstance(FormatOnSaveParticipant), - instantiationService.createInstance(FinalNewLineParticipant), - instantiationService.createInstance(TrimFinalNewLinesParticipant), - instantiationService.createInstance(ExtHostSaveParticipant, extHostContext), - ]); - // Set as save participant for all text files - this._textFileService.saveParticipant = this; + this._saveParticipantDisposable = this._textFileService.files.addSaveParticipant(instantiationService.createInstance(ExtHostSaveParticipant, extHostContext)); } dispose(): void { - this._textFileService.saveParticipant = undefined; - this._saveParticipants.dispose(); - } - - async participate(model: IResolvedTextFileEditorModel, context: { reason: SaveReason; }, token: CancellationToken): Promise { - - const cts = new CancellationTokenSource(token); - - return this._progressService.withProgress({ - title: localize('saveParticipants', "Running Save Participants for '{0}'", this._labelService.getUriLabel(model.resource, { relative: true })), - location: ProgressLocation.Notification, - cancellable: true, - delay: model.isDirty() ? 3000 : 5000 - }, async progress => { - // undoStop before participation - model.textEditorModel.pushStackElement(); - - for (let p of this._saveParticipants.getValue()) { - if (cts.token.isCancellationRequested) { - break; - } - try { - const promise = p.participate(model, context, progress, cts.token); - await raceCancellation(promise, cts.token); - } catch (err) { - this._logService.warn(err); - } - } - - // undoStop after participation - model.textEditorModel.pushStackElement(); - }, () => { - // user cancel - cts.dispose(true); - }); + this._saveParticipantDisposable.dispose(); } } diff --git a/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts b/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts index dc8c179721c4..49cf081d9675 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts @@ -9,6 +9,7 @@ import './diffEditorHelper'; import './inspectKeybindings'; import './largeFileOptimizations'; import './inspectEditorTokens/inspectEditorTokens'; +import './saveParticipants'; import './toggleMinimap'; import './toggleMultiCursorModifier'; import './toggleRenderControlCharacter'; diff --git a/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts b/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts new file mode 100644 index 000000000000..ea6fc68420f3 --- /dev/null +++ b/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts @@ -0,0 +1,316 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancellationToken } from 'vs/base/common/cancellation'; +import * as strings from 'vs/base/common/strings'; +import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { trimTrailingWhitespace } from 'vs/editor/common/commands/trimTrailingWhitespaceCommand'; +import { EditOperation } from 'vs/editor/common/core/editOperation'; +import { Position } from 'vs/editor/common/core/position'; +import { Range } from 'vs/editor/common/core/range'; +import { Selection } from 'vs/editor/common/core/selection'; +import { ITextModel } from 'vs/editor/common/model'; +import { CodeAction, CodeActionTriggerType } from 'vs/editor/common/modes'; +import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction'; +import { applyCodeAction } from 'vs/editor/contrib/codeAction/codeActionCommands'; +import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; +import { formatDocumentWithSelectedProvider, FormattingMode } from 'vs/editor/contrib/format/format'; +import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2'; +import { localize } from 'vs/nls'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IProgressStep, IProgress } from 'vs/platform/progress/common/progress'; +import { IResolvedTextFileEditorModel, ITextFileService, ITextFileSaveParticipant } from 'vs/workbench/services/textfile/common/textfiles'; +import { SaveReason } from 'vs/workbench/common/editor'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IWorkbenchContribution, Extensions as WorkbenchContributionsExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; + +class TrimWhitespaceParticipant implements ITextFileSaveParticipant { + + constructor( + @IConfigurationService private readonly configurationService: IConfigurationService, + @ICodeEditorService private readonly codeEditorService: ICodeEditorService + ) { + // Nothing + } + + async participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason; }): Promise { + if (this.configurationService.getValue('files.trimTrailingWhitespace', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.resource })) { + this.doTrimTrailingWhitespace(model.textEditorModel, env.reason === SaveReason.AUTO); + } + } + + private doTrimTrailingWhitespace(model: ITextModel, isAutoSaved: boolean): void { + let prevSelection: Selection[] = []; + let cursors: Position[] = []; + + const editor = findEditor(model, this.codeEditorService); + if (editor) { + // Find `prevSelection` in any case do ensure a good undo stack when pushing the edit + // Collect active cursors in `cursors` only if `isAutoSaved` to avoid having the cursors jump + prevSelection = editor.getSelections(); + if (isAutoSaved) { + cursors = prevSelection.map(s => s.getPosition()); + const snippetsRange = SnippetController2.get(editor).getSessionEnclosingRange(); + if (snippetsRange) { + for (let lineNumber = snippetsRange.startLineNumber; lineNumber <= snippetsRange.endLineNumber; lineNumber++) { + cursors.push(new Position(lineNumber, model.getLineMaxColumn(lineNumber))); + } + } + } + } + + const ops = trimTrailingWhitespace(model, cursors); + if (!ops.length) { + return; // Nothing to do + } + + model.pushEditOperations(prevSelection, ops, (_edits) => prevSelection); + } +} + +function findEditor(model: ITextModel, codeEditorService: ICodeEditorService): IActiveCodeEditor | null { + let candidate: IActiveCodeEditor | null = null; + + if (model.isAttachedToEditor()) { + for (const editor of codeEditorService.listCodeEditors()) { + if (editor.hasModel() && editor.getModel() === model) { + if (editor.hasTextFocus()) { + return editor; // favour focused editor if there are multiple + } + + candidate = editor; + } + } + } + + return candidate; +} + +export class FinalNewLineParticipant implements ITextFileSaveParticipant { + + constructor( + @IConfigurationService private readonly configurationService: IConfigurationService, + @ICodeEditorService private readonly codeEditorService: ICodeEditorService + ) { + // Nothing + } + + async participate(model: IResolvedTextFileEditorModel, _env: { reason: SaveReason; }): Promise { + if (this.configurationService.getValue('files.insertFinalNewline', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.resource })) { + this.doInsertFinalNewLine(model.textEditorModel); + } + } + + private doInsertFinalNewLine(model: ITextModel): void { + const lineCount = model.getLineCount(); + const lastLine = model.getLineContent(lineCount); + const lastLineIsEmptyOrWhitespace = strings.lastNonWhitespaceIndex(lastLine) === -1; + + if (!lineCount || lastLineIsEmptyOrWhitespace) { + return; + } + + const edits = [EditOperation.insert(new Position(lineCount, model.getLineMaxColumn(lineCount)), model.getEOL())]; + const editor = findEditor(model, this.codeEditorService); + if (editor) { + editor.executeEdits('insertFinalNewLine', edits, editor.getSelections()); + } else { + model.pushEditOperations([], edits, () => null); + } + } +} + +export class TrimFinalNewLinesParticipant implements ITextFileSaveParticipant { + + constructor( + @IConfigurationService private readonly configurationService: IConfigurationService, + @ICodeEditorService private readonly codeEditorService: ICodeEditorService + ) { + // Nothing + } + + async participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason; }): Promise { + if (this.configurationService.getValue('files.trimFinalNewlines', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.resource })) { + this.doTrimFinalNewLines(model.textEditorModel, env.reason === SaveReason.AUTO); + } + } + + /** + * returns 0 if the entire file is empty or whitespace only + */ + private findLastLineWithContent(model: ITextModel): number { + for (let lineNumber = model.getLineCount(); lineNumber >= 1; lineNumber--) { + const lineContent = model.getLineContent(lineNumber); + if (strings.lastNonWhitespaceIndex(lineContent) !== -1) { + // this line has content + return lineNumber; + } + } + // no line has content + return 0; + } + + private doTrimFinalNewLines(model: ITextModel, isAutoSaved: boolean): void { + const lineCount = model.getLineCount(); + + // Do not insert new line if file does not end with new line + if (lineCount === 1) { + return; + } + + let prevSelection: Selection[] = []; + let cannotTouchLineNumber = 0; + const editor = findEditor(model, this.codeEditorService); + if (editor) { + prevSelection = editor.getSelections(); + if (isAutoSaved) { + for (let i = 0, len = prevSelection.length; i < len; i++) { + const positionLineNumber = prevSelection[i].positionLineNumber; + if (positionLineNumber > cannotTouchLineNumber) { + cannotTouchLineNumber = positionLineNumber; + } + } + } + } + + const lastLineNumberWithContent = this.findLastLineWithContent(model); + const deleteFromLineNumber = Math.max(lastLineNumberWithContent + 1, cannotTouchLineNumber + 1); + const deletionRange = model.validateRange(new Range(deleteFromLineNumber, 1, lineCount, model.getLineMaxColumn(lineCount))); + + if (deletionRange.isEmpty()) { + return; + } + + model.pushEditOperations(prevSelection, [EditOperation.delete(deletionRange)], _edits => prevSelection); + + if (editor) { + editor.setSelections(prevSelection); + } + } +} + +class FormatOnSaveParticipant implements ITextFileSaveParticipant { + + constructor( + @IConfigurationService private readonly configurationService: IConfigurationService, + @ICodeEditorService private readonly codeEditorService: ICodeEditorService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + ) { + // Nothing + } + + async participate(editorModel: IResolvedTextFileEditorModel, env: { reason: SaveReason; }, progress: IProgress, token: CancellationToken): Promise { + const model = editorModel.textEditorModel; + const overrides = { overrideIdentifier: model.getLanguageIdentifier().language, resource: model.uri }; + + if (env.reason === SaveReason.AUTO || !this.configurationService.getValue('editor.formatOnSave', overrides)) { + return undefined; + } + + progress.report({ message: localize('formatting', "Formatting") }); + const editorOrModel = findEditor(model, this.codeEditorService) || model; + await this.instantiationService.invokeFunction(formatDocumentWithSelectedProvider, editorOrModel, FormattingMode.Silent, token); + } +} + +class CodeActionOnSaveParticipant implements ITextFileSaveParticipant { + + constructor( + @IConfigurationService private readonly configurationService: IConfigurationService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + ) { } + + async participate(editorModel: IResolvedTextFileEditorModel, env: { reason: SaveReason; }, progress: IProgress, token: CancellationToken): Promise { + if (env.reason === SaveReason.AUTO) { + return undefined; + } + const model = editorModel.textEditorModel; + + const settingsOverrides = { overrideIdentifier: model.getLanguageIdentifier().language, resource: editorModel.resource }; + const setting = this.configurationService.getValue<{ [kind: string]: boolean }>('editor.codeActionsOnSave', settingsOverrides); + if (!setting) { + return undefined; + } + + const codeActionsOnSave = Object.keys(setting) + .filter(x => setting[x]).map(x => new CodeActionKind(x)) + .sort((a, b) => { + if (CodeActionKind.SourceFixAll.contains(a)) { + if (CodeActionKind.SourceFixAll.contains(b)) { + return 0; + } + return -1; + } + if (CodeActionKind.SourceFixAll.contains(b)) { + return 1; + } + return 0; + }); + + if (!codeActionsOnSave.length) { + return undefined; + } + + const excludedActions = Object.keys(setting) + .filter(x => setting[x] === false) + .map(x => new CodeActionKind(x)); + + progress.report({ message: localize('codeaction', "Quick Fixes") }); + await this.applyOnSaveActions(model, codeActionsOnSave, excludedActions, token); + } + + private async applyOnSaveActions(model: ITextModel, codeActionsOnSave: readonly CodeActionKind[], excludes: readonly CodeActionKind[], token: CancellationToken): Promise { + for (const codeActionKind of codeActionsOnSave) { + const actionsToRun = await this.getActionsToRun(model, codeActionKind, excludes, token); + try { + await this.applyCodeActions(actionsToRun.validActions); + } catch { + // Failure to apply a code action should not block other on save actions + } finally { + actionsToRun.dispose(); + } + } + } + + private async applyCodeActions(actionsToRun: readonly CodeAction[]) { + for (const action of actionsToRun) { + await this.instantiationService.invokeFunction(applyCodeAction, action); + } + } + + private getActionsToRun(model: ITextModel, codeActionKind: CodeActionKind, excludes: readonly CodeActionKind[], token: CancellationToken) { + return getCodeActions(model, model.getFullModelRange(), { + type: CodeActionTriggerType.Auto, + filter: { include: codeActionKind, excludes: excludes, includeSourceActions: true }, + }, token); + } +} + +export class SaveParticipantsContribution extends Disposable implements IWorkbenchContribution { + + constructor( + @IInstantiationService private readonly instantiationService: IInstantiationService, + @ITextFileService private readonly textFileService: ITextFileService + ) { + super(); + + this.registerSaveParticipants(); + } + + private registerSaveParticipants(): void { + this._register(this.textFileService.files.addSaveParticipant(this.instantiationService.createInstance(TrimWhitespaceParticipant))); + this._register(this.textFileService.files.addSaveParticipant(this.instantiationService.createInstance(CodeActionOnSaveParticipant))); + this._register(this.textFileService.files.addSaveParticipant(this.instantiationService.createInstance(FormatOnSaveParticipant))); + this._register(this.textFileService.files.addSaveParticipant(this.instantiationService.createInstance(FinalNewLineParticipant))); + this._register(this.textFileService.files.addSaveParticipant(this.instantiationService.createInstance(TrimFinalNewLinesParticipant))); + } +} + +const workbenchContributionsRegistry = Registry.as(WorkbenchContributionsExtensions.Workbench); +workbenchContributionsRegistry.registerWorkbenchContribution(SaveParticipantsContribution, LifecyclePhase.Restored); diff --git a/src/vs/workbench/test/electron-browser/api/mainThreadSaveParticipant.test.ts b/src/vs/workbench/contrib/codeEditor/test/browser/saveParticipant.test.ts similarity index 98% rename from src/vs/workbench/test/electron-browser/api/mainThreadSaveParticipant.test.ts rename to src/vs/workbench/contrib/codeEditor/test/browser/saveParticipant.test.ts index e9c8770111ec..c695bd3eaefe 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadSaveParticipant.test.ts +++ b/src/vs/workbench/contrib/codeEditor/test/browser/saveParticipant.test.ts @@ -5,9 +5,9 @@ import * as assert from 'assert'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { FinalNewLineParticipant, TrimFinalNewLinesParticipant } from 'vs/workbench/api/browser/mainThreadSaveParticipant'; +import { FinalNewLineParticipant, TrimFinalNewLinesParticipant } from 'vs/workbench/contrib/codeEditor/browser/saveParticipants'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; -import { workbenchInstantiationService, TestTextFileService } from 'vs/workbench/test/electron-browser/workbenchTestServices'; +import { workbenchInstantiationService, TestTextFileService } from 'vs/workbench/test/browser/workbenchTestServices'; import { toResource } from 'vs/base/test/common/utils'; import { IModelService } from 'vs/editor/common/services/modelService'; import { Range } from 'vs/editor/common/core/range'; diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 1f6c9878fa5e..c2d6d00c6ce3 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; import { Emitter, AsyncEmitter } from 'vs/base/common/event'; -import { IResult, ITextFileOperationResult, ITextFileService, ITextFileStreamContent, ITextFileEditorModel, ITextFileContent, IResourceEncodings, IReadTextFileOptions, IWriteTextFileOptions, toBufferOrReadable, TextFileOperationError, TextFileOperationResult, FileOperationWillRunEvent, FileOperationDidRunEvent, ITextFileSaveOptions, ITextFileEditorModelManager, ISaveParticipant } from 'vs/workbench/services/textfile/common/textfiles'; +import { IResult, ITextFileOperationResult, ITextFileService, ITextFileStreamContent, ITextFileEditorModel, ITextFileContent, IResourceEncodings, IReadTextFileOptions, IWriteTextFileOptions, toBufferOrReadable, TextFileOperationError, TextFileOperationResult, FileOperationWillRunEvent, FileOperationDidRunEvent, ITextFileSaveOptions, ITextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textfiles'; import { IRevertOptions, IEncodingSupport } from 'vs/workbench/common/editor'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IFileService, FileOperationError, FileOperationResult, IFileStatWithMetadata, ICreateFileOptions, FileOperation } from 'vs/platform/files/common/files'; @@ -69,8 +69,6 @@ export abstract class AbstractTextFileService extends Disposable implements ITex }; })(); - saveParticipant: ISaveParticipant | undefined = undefined; - abstract get encoding(): IResourceEncodings; constructor( diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index 6186973a5b8c..b9ac20401005 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -616,7 +616,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.textEditorModel.pushStackElement(); } - const saveParticipantCancellation = new CancellationTokenSource(); + const saveCancellation = new CancellationTokenSource(); return this.saveSequentializer.setPending(versionId, (async () => { @@ -625,14 +625,26 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // In addition we update our version right after in case it changed because of a model change // // Save participants can also be skipped through API. - if (this.isResolved() && this.textFileService.saveParticipant && !options.skipSaveParticipants) { + if (this.isResolved() && !options.skipSaveParticipants) { try { - await this.textFileService.saveParticipant.participate(this, { reason: options.reason ?? SaveReason.EXPLICIT }, saveParticipantCancellation.token); + await this.textFileService.files.runSaveParticipants(this, { reason: options.reason ?? SaveReason.EXPLICIT }, saveCancellation.token); } catch (error) { - // Ignore + this.logService.error(`[text file model] runSaveParticipants(${versionId}) - resulted in an error: ${error.toString()}`, this.resource.toString()); } } + // It is possible that a subsequent save is cancelling this + // running save. As such we return early when we detect that + // However, we do not pass the token into the file service + // because that is an atomic operation currently without + // cancellation support, so we dispose the cancellation if + // it was not cancelled yet. + if (saveCancellation.token.isCancellationRequested) { + return; + } else { + saveCancellation.dispose(); + } + // We have to protect against being disposed at this point. It could be that the save() operation // was triggerd followed by a dispose() operation right after without waiting. Typically we cannot // be disposed if we are dirty, but if we are not dirty, save() and dispose() can still be triggered @@ -687,7 +699,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.handleSaveError(error, versionId, options); } })()); - })(), () => saveParticipantCancellation.cancel()); + })(), () => saveCancellation.cancel()); } private handleSaveSuccess(stat: IFileStatWithMetadata, versionId: number, options: ITextFileSaveOptions): void { diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts index a4fbde9a277a..12040a85830c 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts @@ -7,7 +7,7 @@ import { Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { dispose, IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { ITextFileEditorModel, ITextFileEditorModelManager, IModelLoadOrCreateOptions, ITextFileModelLoadEvent, ITextFileModelSaveEvent } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileEditorModel, ITextFileEditorModelManager, IModelLoadOrCreateOptions, ITextFileModelLoadEvent, ITextFileModelSaveEvent, ITextFileSaveParticipant, IResolvedTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ResourceMap } from 'vs/base/common/map'; @@ -15,6 +15,9 @@ import { IFileService, FileChangesEvent } from 'vs/platform/files/common/files'; import { distinct, coalesce } from 'vs/base/common/arrays'; import { ResourceQueue } from 'vs/base/common/async'; import { onUnexpectedError } from 'vs/base/common/errors'; +import { TextFileSaveParticipant } from 'vs/workbench/services/textfile/common/textFileSaveParticipant'; +import { SaveReason } from 'vs/workbench/common/editor'; +import { CancellationToken } from 'vs/base/common/cancellation'; export class TextFileEditorModelManager extends Disposable implements ITextFileEditorModelManager { @@ -227,6 +230,20 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE } } + //#region Save participants + + private readonly saveParticipants = this._register(this.instantiationService.createInstance(TextFileSaveParticipant)); + + addSaveParticipant(participant: ITextFileSaveParticipant): IDisposable { + return this.saveParticipants.addSaveParticipant(participant); + } + + runSaveParticipants(model: IResolvedTextFileEditorModel, context: { reason: SaveReason; }, token: CancellationToken): Promise { + return this.saveParticipants.participate(model, context, token); + } + + //#endregion + clear(): void { // model caches diff --git a/src/vs/workbench/services/textfile/common/textFileSaveParticipant.ts b/src/vs/workbench/services/textfile/common/textFileSaveParticipant.ts new file mode 100644 index 000000000000..2ba91174b679 --- /dev/null +++ b/src/vs/workbench/services/textfile/common/textFileSaveParticipant.ts @@ -0,0 +1,69 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { raceCancellation } from 'vs/base/common/async'; +import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation'; +import { localize } from 'vs/nls'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { ITextFileSaveParticipant, IResolvedTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; +import { SaveReason } from 'vs/workbench/common/editor'; +import { IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle'; + +export class TextFileSaveParticipant extends Disposable { + + private readonly saveParticipants: ITextFileSaveParticipant[] = []; + + constructor( + @IProgressService private readonly progressService: IProgressService, + @ILogService private readonly logService: ILogService + ) { + super(); + } + + addSaveParticipant(participant: ITextFileSaveParticipant): IDisposable { + this.saveParticipants.push(participant); + + return toDisposable(() => this.saveParticipants.splice(this.saveParticipants.indexOf(participant), 1)); + } + + participate(model: IResolvedTextFileEditorModel, context: { reason: SaveReason; }, token: CancellationToken): Promise { + const cts = new CancellationTokenSource(token); + + return this.progressService.withProgress({ + title: localize('saveParticipants', "Running Save Participants for '{0}'", model.name), + location: ProgressLocation.Notification, + cancellable: true, + delay: model.isDirty() ? 3000 : 5000 + }, async progress => { + + // undoStop before participation + model.textEditorModel.pushStackElement(); + + for (const saveParticipant of this.saveParticipants) { + if (cts.token.isCancellationRequested) { + break; + } + + try { + const promise = saveParticipant.participate(model, context, progress, cts.token); + await raceCancellation(promise, cts.token); + } catch (err) { + this.logService.warn(err); + } + } + + // undoStop after participation + model.textEditorModel.pushStackElement(); + }, () => { + // user cancel + cts.dispose(true); + }); + } + + dispose(): void { + this.saveParticipants.splice(0, this.saveParticipants.length); + } +} diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index c3c1f68d02ca..d89c66af2c9f 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -17,6 +17,7 @@ import { isNative } from 'vs/base/common/platform'; import { IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { IUntitledTextEditorModelManager } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { IProgress, IProgressStep } from 'vs/platform/progress/common/progress'; export const ITextFileService = createDecorator('textFileService'); @@ -57,11 +58,6 @@ export interface ITextFileService extends IDisposable { */ saveErrorHandler: ISaveErrorHandler; - /** - * The save participant if any. By default, no save participant is registered. - */ - saveParticipant: ISaveParticipant | undefined; - /** * A resource is dirty if it has unsaved changes or is an untitled file not yet saved. * @@ -226,14 +222,6 @@ export interface ISaveErrorHandler { onSaveError(error: Error, model: ITextFileEditorModel): void; } -export interface ISaveParticipant { - - /** - * Participate in a save of a model. Allows to change the model before it is being saved to disk. - */ - participate(model: IResolvedTextFileEditorModel, context: { reason: SaveReason }, token: CancellationToken): Promise; -} - /** * States the text file editor model can be in. */ @@ -357,6 +345,20 @@ export interface ITextFileModelLoadEvent { reason: LoadReason; } +export interface ITextFileSaveParticipant { + + /** + * Participate in a save of a model. Allows to change the model + * before it is being saved to disk. + */ + participate( + model: IResolvedTextFileEditorModel, + context: { reason: SaveReason }, + progress: IProgress, + token: CancellationToken + ): Promise; +} + export interface ITextFileEditorModelManager { readonly onDidLoad: Event; @@ -372,6 +374,9 @@ export interface ITextFileEditorModelManager { resolve(resource: URI, options?: IModelLoadOrCreateOptions): Promise; + addSaveParticipant(participant: ITextFileSaveParticipant): IDisposable; + runSaveParticipants(model: IResolvedTextFileEditorModel, context: { reason: SaveReason; }, token: CancellationToken): Promise + disposeModel(model: ITextFileEditorModel): void; } diff --git a/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts b/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts index c835deecece1..af2880b2e160 100644 --- a/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts +++ b/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts @@ -502,55 +502,98 @@ suite('Files - TextFileEditorModel', () => { eventCounter++; }); - accessor.textFileService.saveParticipant = { + const participant = accessor.textFileService.files.addSaveParticipant({ participate: async model => { assert.ok(model.isDirty()); model.textEditorModel!.setValue('bar'); assert.ok(model.isDirty()); eventCounter++; } - }; + }); await model.load(); model.textEditorModel!.setValue('foo'); await model.save(); - model.dispose(); assert.equal(eventCounter, 2); + + participant.dispose(); + model.textEditorModel!.setValue('bar'); + + await model.save(); + assert.equal(eventCounter, 3); + + model.dispose(); + }); + + test('Save Participant - skip', async function () { + let eventCounter = 0; + const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); + + const participant = accessor.textFileService.files.addSaveParticipant({ + participate: async model => { + eventCounter++; + } + }); + + await model.load(); + model.textEditorModel!.setValue('foo'); + + await model.save({ skipSaveParticipants: true }); + assert.equal(eventCounter, 0); + + participant.dispose(); + model.dispose(); }); test('Save Participant, async participant', async function () { + let eventCounter = 0; const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - accessor.textFileService.saveParticipant = { - participate: (model) => { + model.onDidSave(e => { + assert.ok(!model.isDirty()); + eventCounter++; + }); + + const participant = accessor.textFileService.files.addSaveParticipant({ + participate: model => { + assert.ok(model.isDirty()); + model.textEditorModel!.setValue('bar'); + assert.ok(model.isDirty()); + eventCounter++; + return timeout(10); } - }; + }); await model.load(); model.textEditorModel!.setValue('foo'); const now = Date.now(); await model.save(); + assert.equal(eventCounter, 2); assert.ok(Date.now() - now >= 10); + model.dispose(); + participant.dispose(); }); test('Save Participant, bad participant', async function () { const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - accessor.textFileService.saveParticipant = { + const participant = accessor.textFileService.files.addSaveParticipant({ participate: async model => { new Error('boom'); } - }; + }); await model.load(); model.textEditorModel!.setValue('foo'); await model.save(); + model.dispose(); + participant.dispose(); }); test('Save Participant, participant cancelled when saved again', async function () { @@ -558,12 +601,15 @@ suite('Files - TextFileEditorModel', () => { let participations: boolean[] = []; - accessor.textFileService.saveParticipant = { - participate: async model => { + const participant = accessor.textFileService.files.addSaveParticipant({ + participate: async (model, context, progress, token) => { await timeout(10); - participations.push(true); + + if (!token.isCancellationRequested) { + participations.push(true); + } } - }; + }); await model.load(); @@ -574,12 +620,16 @@ suite('Files - TextFileEditorModel', () => { const p2 = model.save(); model.textEditorModel!.setValue('foo 2'); - await model.save(); + const p3 = model.save(); - await p1; - await p2; + model.textEditorModel!.setValue('foo 3'); + const p4 = model.save(); + + await Promise.all([p1, p2, p3, p4]); assert.equal(participations.length, 1); + model.dispose(); + participant.dispose(); }); test('Save Participant, calling save from within is unsupported but does not explode (sync save)', async function () { @@ -602,7 +652,7 @@ suite('Files - TextFileEditorModel', () => { let savePromise: Promise; let breakLoop = false; - accessor.textFileService.saveParticipant = { + const participant = accessor.textFileService.files.addSaveParticipant({ participate: async model => { if (breakLoop) { return; @@ -618,12 +668,14 @@ suite('Files - TextFileEditorModel', () => { // assert that this is the same promise as the outer one assert.equal(savePromise, newSavePromise); } - }; + }); await model.load(); model.textEditorModel!.setValue('foo'); savePromise = model.save(); await savePromise; + + participant.dispose(); } }); diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 67ef5397a994..783d4d74a564 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -89,6 +89,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel'; import { IRemotePathService } from 'vs/workbench/services/path/common/remotePathService'; import { Direction } from 'vs/base/browser/ui/grid/grid'; +import { IProgressService, IProgressOptions, IProgressWindowOptions, IProgressNotificationOptions, IProgressCompositeOptions, IProgress, IProgressStep, emptyProgress } from 'vs/platform/progress/common/progress'; export import TestTextResourcePropertiesService = CommonWorkbenchTestServices.TestTextResourcePropertiesService; export import TestContextService = CommonWorkbenchTestServices.TestContextService; @@ -175,6 +176,7 @@ export function workbenchInstantiationService(overrides?: { textFileService?: (i instantiationService.stub(IEnvironmentService, TestEnvironmentService); const contextKeyService = instantiationService.createInstance(MockContextKeyService); instantiationService.stub(IContextKeyService, contextKeyService); + instantiationService.stub(IProgressService, new TestProgressService()); const workspaceContextService = new TestContextService(TestWorkspace); instantiationService.stub(IWorkspaceContextService, workspaceContextService); const configService = new TestConfigurationService(); @@ -217,6 +219,19 @@ export function workbenchInstantiationService(overrides?: { textFileService?: (i return instantiationService; } +export class TestProgressService implements IProgressService { + + _serviceBrand: undefined; + + withProgress( + options: IProgressOptions | IProgressWindowOptions | IProgressNotificationOptions | IProgressCompositeOptions, + task: (progress: IProgress) => Promise, + onDidCancel?: ((choice?: number | undefined) => void) | undefined + ): Promise { + return task(emptyProgress); + } +} + export class TestAccessibilityService implements IAccessibilityService { _serviceBrand: undefined; From 42e21c2e6332dd81e00055d69c7116eec4b63b45 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 17 Feb 2020 10:44:25 +0100 Subject: [PATCH 083/109] electron - configure to disable LayoutNG (workaround for #88873) --- src/main.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main.js b/src/main.js index 0e90f4504c83..6a6cb060acc1 100644 --- a/src/main.js +++ b/src/main.js @@ -82,7 +82,7 @@ app.once('ready', function () { traceOptions: args['trace-options'] || 'record-until-full,enable-sampling' }; - contentTracing.startRecording(traceOptions, () => onReady()); + contentTracing.startRecording(traceOptions).finally(() => onReady()); } else { onReady(); } @@ -162,6 +162,9 @@ function configureCommandlineSwitchesSync(cliArgs) { app.commandLine.appendSwitch('js-flags', jsFlags); } + // TODO@Ben TODO@Deepak Electron 7 workaround for https://github.com/microsoft/vscode/issues/88873 + app.commandLine.appendSwitch('disable-features', 'LayoutNG'); + return argvConfig; } From d9de4afd7a5bdeae266b06a5c98b227c1e9e88c7 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 17 Feb 2020 10:46:07 +0100 Subject: [PATCH 084/109] progress - remove unused CSS rules --- .../services/progress/browser/media/progressService.css | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/vs/workbench/services/progress/browser/media/progressService.css b/src/vs/workbench/services/progress/browser/media/progressService.css index 6b73a2f3a4e2..a1e92a76439f 100644 --- a/src/vs/workbench/services/progress/browser/media/progressService.css +++ b/src/vs/workbench/services/progress/browser/media/progressService.css @@ -3,14 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-workbench .part.statusbar > .items-container > .statusbar-item.progress { - padding-left: 5px; -} - -.monaco-workbench .part.statusbar > .items-container > .statusbar-item.progress .spinner-container { - padding-right: 5px; -} - .monaco-workbench .progress-badge > .badge-content::before { mask: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNCIgaGVpZ2h0PSIxNCIgdmlld0JveD0iMiAyIDE0IDE0IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDIgMiAxNCAxNCI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTkgMTZjLTMuODYgMC03LTMuMTQtNy03czMuMTQtNyA3LTdjMy44NTkgMCA3IDMuMTQxIDcgN3MtMy4xNDEgNy03IDd6bTAtMTIuNmMtMy4wODggMC01LjYgMi41MTMtNS42IDUuNnMyLjUxMiA1LjYgNS42IDUuNiA1LjYtMi41MTIgNS42LTUuNi0yLjUxMi01LjYtNS42LTUuNnptMy44NiA3LjFsLTMuMTYtMS44OTZ2LTMuODA0aC0xLjR2NC41OTZsMy44NCAyLjMwNS43Mi0xLjIwMXoiLz48L3N2Zz4="); -webkit-mask: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNCIgaGVpZ2h0PSIxNCIgdmlld0JveD0iMiAyIDE0IDE0IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDIgMiAxNCAxNCI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTkgMTZjLTMuODYgMC03LTMuMTQtNy03czMuMTQtNyA3LTdjMy44NTkgMCA3IDMuMTQxIDcgN3MtMy4xNDEgNy03IDd6bTAtMTIuNmMtMy4wODggMC01LjYgMi41MTMtNS42IDUuNnMyLjUxMiA1LjYgNS42IDUuNiA1LjYtMi41MTIgNS42LTUuNi0yLjUxMi01LjYtNS42LTUuNnptMy44NiA3LjFsLTMuMTYtMS44OTZ2LTMuODA0aC0xLjR2NC41OTZsMy44NCAyLjMwNS43Mi0xLjIwMXoiLz48L3N2Zz4="); From 0f42828c4f512e8401512e0c12e27c85e4361115 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 17 Feb 2020 11:02:19 +0100 Subject: [PATCH 085/109] types - reduce type duplication --- .../notification/common/notification.ts | 10 ++-- src/vs/workbench/browser/labels.ts | 2 +- src/vs/workbench/browser/layout.ts | 28 +++++------ src/vs/workbench/browser/part.ts | 2 +- .../workbench/browser/parts/compositeBar.ts | 4 +- .../browser/parts/editor/baseEditor.ts | 2 +- .../browser/parts/editor/editorGroupView.ts | 2 +- .../browser/parts/panel/panelPart.ts | 2 +- src/vs/workbench/browser/viewlet.ts | 2 +- src/vs/workbench/browser/workbench.ts | 8 +-- src/vs/workbench/common/notifications.ts | 49 +++++++++---------- src/vs/workbench/common/resources.ts | 6 +-- 12 files changed, 55 insertions(+), 62 deletions(-) diff --git a/src/vs/platform/notification/common/notification.ts b/src/vs/platform/notification/common/notification.ts index 93429296cfed..aff1675b214c 100644 --- a/src/vs/platform/notification/common/notification.ts +++ b/src/vs/platform/notification/common/notification.ts @@ -6,7 +6,7 @@ import BaseSeverity from 'vs/base/common/severity'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IAction } from 'vs/base/common/actions'; -import { Event, Emitter } from 'vs/base/common/event'; +import { Event } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; export import Severity = BaseSeverity; @@ -318,16 +318,14 @@ export class NoOpNotification implements INotificationHandle { readonly progress = new NoOpProgress(); - private readonly _onDidClose: Emitter = new Emitter(); - readonly onDidClose: Event = this._onDidClose.event; + readonly onDidClose = Event.None; + readonly onDidChangeVisibility = Event.None; updateSeverity(severity: Severity): void { } updateMessage(message: NotificationMessage): void { } updateActions(actions?: INotificationActions): void { } - close(): void { - this._onDidClose.dispose(); - } + close(): void { } } export class NoOpProgress implements INotificationProgress { diff --git a/src/vs/workbench/browser/labels.ts b/src/vs/workbench/browser/labels.ts index 5c87849e9ea8..bcbae8f4e2dd 100644 --- a/src/vs/workbench/browser/labels.ts +++ b/src/vs/workbench/browser/labels.ts @@ -239,7 +239,7 @@ enum Redraw { class ResourceLabelWidget extends IconLabel { private _onDidRender = this._register(new Emitter()); - readonly onDidRender: Event = this._onDidRender.event; + readonly onDidRender = this._onDidRender.event; private readonly renderDisposables = this._register(new DisposableStore()); diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index f94fe18534fc..66320713617f 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { Event, Emitter } from 'vs/base/common/event'; +import { Emitter } from 'vs/base/common/event'; import { EventType, addDisposableListener, addClass, removeClass, isAncestor, getClientArea, Dimension, toggleClass, position, size } from 'vs/base/browser/dom'; import { onDidChangeFullscreen, isFullscreen } from 'vs/base/browser/browser'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; @@ -87,26 +87,26 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi //#region Events - private readonly _onZenModeChange: Emitter = this._register(new Emitter()); - readonly onZenModeChange: Event = this._onZenModeChange.event; + private readonly _onZenModeChange = this._register(new Emitter()); + readonly onZenModeChange = this._onZenModeChange.event; - private readonly _onFullscreenChange: Emitter = this._register(new Emitter()); - readonly onFullscreenChange: Event = this._onFullscreenChange.event; + private readonly _onFullscreenChange = this._register(new Emitter()); + readonly onFullscreenChange = this._onFullscreenChange.event; - private readonly _onCenteredLayoutChange: Emitter = this._register(new Emitter()); - readonly onCenteredLayoutChange: Event = this._onCenteredLayoutChange.event; + private readonly _onCenteredLayoutChange = this._register(new Emitter()); + readonly onCenteredLayoutChange = this._onCenteredLayoutChange.event; - private readonly _onMaximizeChange: Emitter = this._register(new Emitter()); - readonly onMaximizeChange: Event = this._onMaximizeChange.event; + private readonly _onMaximizeChange = this._register(new Emitter()); + readonly onMaximizeChange = this._onMaximizeChange.event; - private readonly _onPanelPositionChange: Emitter = this._register(new Emitter()); - readonly onPanelPositionChange: Event = this._onPanelPositionChange.event; + private readonly _onPanelPositionChange = this._register(new Emitter()); + readonly onPanelPositionChange = this._onPanelPositionChange.event; - private readonly _onPartVisibilityChange: Emitter = this._register(new Emitter()); - readonly onPartVisibilityChange: Event = this._onPartVisibilityChange.event; + private readonly _onPartVisibilityChange = this._register(new Emitter()); + readonly onPartVisibilityChange = this._onPartVisibilityChange.event; private readonly _onLayout = this._register(new Emitter()); - readonly onLayout: Event = this._onLayout.event; + readonly onLayout = this._onLayout.event; //#endregion diff --git a/src/vs/workbench/browser/part.ts b/src/vs/workbench/browser/part.ts index 1e07ec234517..cec01038567c 100644 --- a/src/vs/workbench/browser/part.ts +++ b/src/vs/workbench/browser/part.ts @@ -34,7 +34,7 @@ export abstract class Part extends Component implements ISerializableView { get dimension(): Dimension | undefined { return this._dimension; } protected _onDidVisibilityChange = this._register(new Emitter()); - readonly onDidVisibilityChange: Event = this._onDidVisibilityChange.event; + readonly onDidVisibilityChange = this._onDidVisibilityChange.event; private parent: HTMLElement | undefined; private titleArea: HTMLElement | undefined; diff --git a/src/vs/workbench/browser/parts/compositeBar.ts b/src/vs/workbench/browser/parts/compositeBar.ts index 4f824d94c74a..4ed709af0805 100644 --- a/src/vs/workbench/browser/parts/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositeBar.ts @@ -19,7 +19,7 @@ import { Widget } from 'vs/base/browser/ui/widget'; import { isUndefinedOrNull } from 'vs/base/common/types'; import { LocalSelectionTransfer } from 'vs/workbench/browser/dnd'; import { ITheme } from 'vs/platform/theme/common/themeService'; -import { Emitter, Event } from 'vs/base/common/event'; +import { Emitter } from 'vs/base/common/event'; export interface ICompositeBarItem { id: string; @@ -61,7 +61,7 @@ export class CompositeBar extends Widget implements ICompositeBar { private compositeTransfer: LocalSelectionTransfer; private readonly _onDidChange: Emitter = this._register(new Emitter()); - readonly onDidChange: Event = this._onDidChange.event; + readonly onDidChange = this._onDidChange.event; constructor( items: ICompositeBarItem[], diff --git a/src/vs/workbench/browser/parts/editor/baseEditor.ts b/src/vs/workbench/browser/parts/editor/baseEditor.ts index 583bdb727a74..19781fbb59f9 100644 --- a/src/vs/workbench/browser/parts/editor/baseEditor.ts +++ b/src/vs/workbench/browser/parts/editor/baseEditor.ts @@ -39,7 +39,7 @@ export abstract class BaseEditor extends Panel implements IEditor { readonly minimumHeight = DEFAULT_EDITOR_MIN_DIMENSIONS.height; readonly maximumHeight = DEFAULT_EDITOR_MAX_DIMENSIONS.height; - readonly onDidSizeConstraintsChange: Event<{ width: number; height: number; } | undefined> = Event.None; + readonly onDidSizeConstraintsChange = Event.None; protected _input: EditorInput | undefined; protected _options: EditorOptions | undefined; diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 11f5d5a0a04e..cb05c88239b5 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -1580,7 +1580,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { get maximumHeight(): number { return this.editorControl.maximumHeight; } private _onDidChange = this._register(new Relay<{ width: number; height: number; } | undefined>()); - readonly onDidChange: Event<{ width: number; height: number; } | undefined> = this._onDidChange.event; + readonly onDidChange = this._onDidChange.event; layout(width: number, height: number): void { this.dimension = new Dimension(width, height); diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index f9d079be854b..6ee4f8cff776 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -76,7 +76,7 @@ export class PanelPart extends CompositePart implements IPanelService { //#endregion get onDidPanelOpen(): Event<{ panel: IPanel, focus: boolean; }> { return Event.map(this.onDidCompositeOpen.event, compositeOpen => ({ panel: compositeOpen.composite, focus: compositeOpen.focus })); } - readonly onDidPanelClose: Event = this.onDidCompositeClose.event; + readonly onDidPanelClose = this.onDidCompositeClose.event; private activePanelContextKey: IContextKey; private panelFocusContextKey: IContextKey; diff --git a/src/vs/workbench/browser/viewlet.ts b/src/vs/workbench/browser/viewlet.ts index 2ea6bcae00fe..ddcb76a0a955 100644 --- a/src/vs/workbench/browser/viewlet.ts +++ b/src/vs/workbench/browser/viewlet.ts @@ -68,7 +68,7 @@ export abstract class Viewlet extends PaneComposite implements IViewlet { */ export class ViewletDescriptor extends CompositeDescriptor { - public static create( + static create( ctor: { new(...services: Services): Viewlet }, id: string, name: string, diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index 604e4d1e9142..a24435cd2417 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -6,7 +6,7 @@ import 'vs/workbench/browser/style'; import { localize } from 'vs/nls'; -import { Event, Emitter, setGlobalLeakWarningThreshold } from 'vs/base/common/event'; +import { Emitter, setGlobalLeakWarningThreshold } from 'vs/base/common/event'; import { addClasses, addClass, removeClasses } from 'vs/base/browser/dom'; import { runWhenIdle } from 'vs/base/common/async'; import { getZoomLevel, isFirefox, isSafari, isChrome } from 'vs/base/browser/browser'; @@ -50,13 +50,13 @@ import { Extensions as PanelExtensions, PanelRegistry } from 'vs/workbench/brows export class Workbench extends Layout { private readonly _onBeforeShutdown = this._register(new Emitter()); - readonly onBeforeShutdown: Event = this._onBeforeShutdown.event; + readonly onBeforeShutdown = this._onBeforeShutdown.event; private readonly _onWillShutdown = this._register(new Emitter()); - readonly onWillShutdown: Event = this._onWillShutdown.event; + readonly onWillShutdown = this._onWillShutdown.event; private readonly _onShutdown = this._register(new Emitter()); - readonly onShutdown: Event = this._onShutdown.event; + readonly onShutdown = this._onShutdown.event; constructor( parent: HTMLElement, diff --git a/src/vs/workbench/common/notifications.ts b/src/vs/workbench/common/notifications.ts index a6b19e5df2c8..01d7271a3b9a 100644 --- a/src/vs/workbench/common/notifications.ts +++ b/src/vs/workbench/common/notifications.ts @@ -16,9 +16,7 @@ import { find, equals } from 'vs/base/common/arrays'; export interface INotificationsModel { - // - // Notifications as Toasts/Center - // + //#region Notifications as Toasts/Center readonly notifications: INotificationViewItem[]; @@ -29,15 +27,18 @@ export interface INotificationsModel { setFilter(filter: NotificationsFilter): void; - // - // Notifications as Status - // + //#endregion + + + //#region Notifications as Status readonly statusMessage: IStatusMessageViewItem | undefined; readonly onDidStatusMessageChange: Event; showStatusMessage(message: NotificationMessage, options?: IStatusMessageOptions): IDisposable; + + //#endregion } export const enum NotificationChangeType { @@ -130,13 +131,13 @@ export class NotificationsModel extends Disposable implements INotificationsMode private static readonly NO_OP_NOTIFICATION = new NoOpNotification(); private readonly _onDidNotificationChange = this._register(new Emitter()); - readonly onDidNotificationChange: Event = this._onDidNotificationChange.event; + readonly onDidNotificationChange = this._onDidNotificationChange.event; private readonly _onDidStatusMessageChange = this._register(new Emitter()); - readonly onDidStatusMessageChange: Event = this._onDidStatusMessageChange.event; + readonly onDidStatusMessageChange = this._onDidStatusMessageChange.event; private readonly _onDidFilterChange = this._register(new Emitter()); - readonly onDidFilterChange: Event = this._onDidFilterChange.event; + readonly onDidFilterChange = this._onDidFilterChange.event; private readonly _notifications: INotificationViewItem[] = []; get notifications(): INotificationViewItem[] { return this._notifications; } @@ -171,10 +172,10 @@ export class NotificationsModel extends Disposable implements INotificationsMode this._onDidNotificationChange.fire({ item, index: 0, kind: NotificationChangeType.ADD }); // Wrap into handle - return new NotificationHandle(item, item => this.closeItem(item)); + return new NotificationHandle(item, item => this.onClose(item)); } - private closeItem(item: INotificationViewItem): void { + private onClose(item: INotificationViewItem): void { const liveItem = this.findNotification(item); if (liveItem && liveItem !== item) { liveItem.close(); // item could have been replaced with another one, make sure to close the live item @@ -309,8 +310,8 @@ export interface INotificationViewItemProgress extends INotificationProgress { export class NotificationViewItemProgress extends Disposable implements INotificationViewItemProgress { private readonly _state: INotificationViewItemProgressState; - private readonly _onDidChange: Emitter = this._register(new Emitter()); - readonly onDidChange: Event = this._onDidChange.event; + private readonly _onDidChange = this._register(new Emitter()); + readonly onDidChange = this._onDidChange.event; constructor() { super(); @@ -405,14 +406,14 @@ export class NotificationViewItem extends Disposable implements INotificationVie private _actions: INotificationActions | undefined; private _progress: NotificationViewItemProgress | undefined; - private readonly _onDidExpansionChange: Emitter = this._register(new Emitter()); - readonly onDidExpansionChange: Event = this._onDidExpansionChange.event; + private readonly _onDidExpansionChange = this._register(new Emitter()); + readonly onDidExpansionChange = this._onDidExpansionChange.event; - private readonly _onDidClose: Emitter = this._register(new Emitter()); - readonly onDidClose: Event = this._onDidClose.event; + private readonly _onDidClose = this._register(new Emitter()); + readonly onDidClose = this._onDidClose.event; - private readonly _onDidLabelChange: Emitter = this._register(new Emitter()); - readonly onDidLabelChange: Event = this._onDidLabelChange.event; + private readonly _onDidLabelChange = this._register(new Emitter()); + readonly onDidLabelChange = this._onDidLabelChange.event; static create(notification: INotification, filter: NotificationsFilter = NotificationsFilter.OFF): INotificationViewItem | undefined { if (!notification || !notification.message || isPromiseCanceledError(notification.message)) { @@ -656,8 +657,8 @@ export class NotificationViewItem extends Disposable implements INotificationVie export class ChoiceAction extends Action { - private readonly _onDidRun = new Emitter(); - readonly onDidRun: Event = this._onDidRun.event; + private readonly _onDidRun = this._register(new Emitter()); + readonly onDidRun = this._onDidRun.event; private readonly _keepOpen: boolean; @@ -679,12 +680,6 @@ export class ChoiceAction extends Action { get keepOpen(): boolean { return this._keepOpen; } - - dispose(): void { - super.dispose(); - - this._onDidRun.dispose(); - } } class StatusMessageViewItem { diff --git a/src/vs/workbench/common/resources.ts b/src/vs/workbench/common/resources.ts index c509716fc499..408dcca11ac4 100644 --- a/src/vs/workbench/common/resources.ts +++ b/src/vs/workbench/common/resources.ts @@ -5,7 +5,7 @@ import { URI } from 'vs/base/common/uri'; import * as objects from 'vs/base/common/objects'; -import { Event, Emitter } from 'vs/base/common/event'; +import { Emitter } from 'vs/base/common/event'; import { basename, extname, relativePath } from 'vs/base/common/resources'; import { RawContextKey, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IModeService } from 'vs/editor/common/services/modeService'; @@ -106,8 +106,8 @@ export class ResourceGlobMatcher extends Disposable { private static readonly NO_ROOT: string | null = null; - private readonly _onExpressionChange: Emitter = this._register(new Emitter()); - readonly onExpressionChange: Event = this._onExpressionChange.event; + private readonly _onExpressionChange = this._register(new Emitter()); + readonly onExpressionChange = this._onExpressionChange.event; private readonly mapRootToParsedExpression: Map = new Map(); private readonly mapRootToExpressionConfig: Map = new Map(); From 8d66874a193e86663587380063753db44e3a0674 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 17 Feb 2020 11:03:21 +0100 Subject: [PATCH 086/109] debt - use progress service for progress notifications --- .../extensions/browser/extensionUrlHandler.ts | 42 +++++++------------ 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts index 41423b188fa6..01d63f94a76a 100644 --- a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts +++ b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts @@ -13,7 +13,7 @@ import { IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementSer import { IWorkbenchExtensionEnablementService, EnablementState } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { INotificationHandle, INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IURLHandler, IURLService, IOpenURLOptions } from 'vs/platform/url/common/url'; import { IHostService } from 'vs/workbench/services/host/browser/host'; @@ -26,6 +26,7 @@ import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; const FIVE_MINUTES = 5 * 60 * 1000; const THIRTY_SECONDS = 30 * 1000; @@ -100,7 +101,8 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { @IHostService private readonly hostService: IHostService, @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, @IStorageService private readonly storageService: IStorageService, - @IConfigurationService private readonly configurationService: IConfigurationService + @IConfigurationService private readonly configurationService: IConfigurationService, + @IProgressService private readonly progressService: IProgressService ) { this.storage = new ConfirmedExtensionIdStorage(storageService); @@ -273,32 +275,20 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { return; } - let notificationHandle: INotificationHandle | null = this.notificationService.notify({ severity: Severity.Info, message: localize('Installing', "Installing Extension '{0}'...", galleryExtension.displayName || galleryExtension.name) }); - notificationHandle.progress.infinite(); - notificationHandle.onDidClose(() => notificationHandle = null); - try { - await this.extensionManagementService.installFromGallery(galleryExtension); - const reloadMessage = localize('reload', "Would you like to reload the window and open the URL '{0}'?", uri.toString()); - const reloadActionLabel = localize('Reload', "Reload Window and Open"); + await this.progressService.withProgress({ + location: ProgressLocation.Notification, + title: localize('Installing', "Installing Extension '{0}'...", galleryExtension.displayName || galleryExtension.name) + }, () => this.extensionManagementService.installFromGallery(galleryExtension)); - if (notificationHandle) { - notificationHandle.progress.done(); - notificationHandle.updateMessage(reloadMessage); - notificationHandle.updateActions({ - primary: [new Action('reloadWindow', reloadActionLabel, undefined, true, () => this.reloadAndHandle(uri))] - }); - } else { - this.notificationService.prompt(Severity.Info, reloadMessage, [{ label: reloadActionLabel, run: () => this.reloadAndHandle(uri) }], { sticky: true }); - } - } catch (e) { - if (notificationHandle) { - notificationHandle.progress.done(); - notificationHandle.updateSeverity(Severity.Error); - notificationHandle.updateMessage(e); - } else { - this.notificationService.error(e); - } + this.notificationService.prompt( + Severity.Info, + localize('reload', "Would you like to reload the window and open the URL '{0}'?", uri.toString()), + [{ label: localize('Reload', "Reload Window and Open"), run: () => this.reloadAndHandle(uri) }], + { sticky: true } + ); + } catch (error) { + this.notificationService.error(error); } } } From 25b36c1c375b967c012fd65e74e352537ba33cc9 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 17 Feb 2020 11:25:17 +0100 Subject: [PATCH 087/109] rename empty view contents to view welcome --- build/lib/i18n.resources.json | 4 - extensions/git/package.json | 56 +++++---- .../common/codeActions.contribution.ts | 4 + .../common/documentationContribution.ts | 84 ++++++++++++++ .../common/documentationExtensionPoint.ts | 35 ------ .../common/documentationContribution.ts | 108 ------------------ .../common/viewsWelcome.contribution.ts} | 8 +- .../common/viewsWelcomeContribution.ts | 46 ++++++++ .../common/viewsWelcomeExtensionPoint.ts | 53 +++++++++ src/vs/workbench/workbench.common.main.ts | 4 +- 10 files changed, 220 insertions(+), 182 deletions(-) create mode 100644 src/vs/workbench/contrib/codeActions/common/documentationContribution.ts rename src/vs/workbench/contrib/{documentation => codeActions}/common/documentationExtensionPoint.ts (65%) delete mode 100644 src/vs/workbench/contrib/documentation/common/documentationContribution.ts rename src/vs/workbench/contrib/{documentation/common/documentation.contribution.ts => welcome/common/viewsWelcome.contribution.ts} (68%) create mode 100644 src/vs/workbench/contrib/welcome/common/viewsWelcomeContribution.ts create mode 100644 src/vs/workbench/contrib/welcome/common/viewsWelcomeExtensionPoint.ts diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index 715a478c1354..fe693b695b40 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -62,10 +62,6 @@ "name": "vs/workbench/contrib/debug", "project": "vscode-workbench" }, - { - "name": "vs/workbench/contrib/documentation", - "project": "vscode-workbench" - }, { "name": "vs/workbench/contrib/emmet", "project": "vscode-workbench" diff --git a/extensions/git/package.json b/extensions/git/package.json index 922a16d0bf0b..596f04228870 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1762,35 +1762,33 @@ ] } }, - "documentation": { - "view": [ - { - "view": "workbench.scm", - "contents": "%view.workbench.scm.disabled%", - "when": "!config.git.enabled" - }, - { - "view": "workbench.scm", - "contents": "%view.workbench.scm.missing%", - "when": "config.git.enabled && git.missing" - }, - { - "view": "workbench.scm", - "contents": "%view.workbench.scm.empty%", - "when": "config.git.enabled && !git.missing && workbenchState == empty" - }, - { - "view": "workbench.scm", - "contents": "%view.workbench.scm.folder%", - "when": "config.git.enabled && !git.missing && workbenchState == folder" - }, - { - "view": "workbench.scm", - "contents": "%view.workbench.scm.workspace%", - "when": "config.git.enabled && !git.missing && workbenchState == workspace" - } - ] - } + "viewsWelcome": [ + { + "view": "workbench.scm", + "contents": "%view.workbench.scm.disabled%", + "when": "!config.git.enabled" + }, + { + "view": "workbench.scm", + "contents": "%view.workbench.scm.missing%", + "when": "config.git.enabled && git.missing" + }, + { + "view": "workbench.scm", + "contents": "%view.workbench.scm.empty%", + "when": "config.git.enabled && !git.missing && workbenchState == empty" + }, + { + "view": "workbench.scm", + "contents": "%view.workbench.scm.folder%", + "when": "config.git.enabled && !git.missing && workbenchState == folder" + }, + { + "view": "workbench.scm", + "contents": "%view.workbench.scm.workspace%", + "when": "config.git.enabled && !git.missing && workbenchState == workspace" + } + ] }, "dependencies": { "byline": "^5.0.0", diff --git a/src/vs/workbench/contrib/codeActions/common/codeActions.contribution.ts b/src/vs/workbench/contrib/codeActions/common/codeActions.contribution.ts index ccdbb435b250..c7969c9d1c0b 100644 --- a/src/vs/workbench/contrib/codeActions/common/codeActions.contribution.ts +++ b/src/vs/workbench/contrib/codeActions/common/codeActions.contribution.ts @@ -10,9 +10,12 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { CodeActionsContribution, editorConfiguration } from 'vs/workbench/contrib/codeActions/common/codeActionsContribution'; import { CodeActionsExtensionPoint, codeActionsExtensionPointDescriptor } from 'vs/workbench/contrib/codeActions/common/codeActionsExtensionPoint'; +import { CodeActionDocumentationContribution } from 'vs/workbench/contrib/codeActions/common/documentationContribution'; +import { DocumentationExtensionPoint, documentationExtensionPointDescriptor } from 'vs/workbench/contrib/codeActions/common/documentationExtensionPoint'; import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; const codeActionsExtensionPoint = ExtensionsRegistry.registerExtensionPoint(codeActionsExtensionPointDescriptor); +const documentationExtensionPoint = ExtensionsRegistry.registerExtensionPoint(documentationExtensionPointDescriptor); Registry.as(Extensions.Configuration) .registerConfiguration(editorConfiguration); @@ -22,6 +25,7 @@ class WorkbenchConfigurationContribution { @IInstantiationService instantiationService: IInstantiationService, ) { instantiationService.createInstance(CodeActionsContribution, codeActionsExtensionPoint); + instantiationService.createInstance(CodeActionDocumentationContribution, documentationExtensionPoint); } } diff --git a/src/vs/workbench/contrib/codeActions/common/documentationContribution.ts b/src/vs/workbench/contrib/codeActions/common/documentationContribution.ts new file mode 100644 index 000000000000..9c2b75c277dc --- /dev/null +++ b/src/vs/workbench/contrib/codeActions/common/documentationContribution.ts @@ -0,0 +1,84 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancellationToken } from 'vs/base/common/cancellation'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { Range } from 'vs/editor/common/core/range'; +import { Selection } from 'vs/editor/common/core/selection'; +import { ITextModel } from 'vs/editor/common/model'; +import * as modes from 'vs/editor/common/modes'; +import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; +import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { IExtensionPoint } from 'vs/workbench/services/extensions/common/extensionsRegistry'; +import { DocumentationExtensionPoint } from './documentationExtensionPoint'; + + +export class CodeActionDocumentationContribution extends Disposable implements IWorkbenchContribution, modes.CodeActionProvider { + + private contributions: { + title: string; + when: ContextKeyExpr; + command: string; + }[] = []; + + private readonly emptyCodeActionsList = { + actions: [], + dispose: () => { } + }; + + constructor( + extensionPoint: IExtensionPoint, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + ) { + super(); + + this._register(modes.CodeActionProviderRegistry.register('*', this)); + + extensionPoint.setHandler(points => { + this.contributions = []; + for (const documentation of points) { + if (!documentation.value.refactoring) { + continue; + } + + for (const contribution of documentation.value.refactoring) { + const precondition = ContextKeyExpr.deserialize(contribution.when); + if (!precondition) { + continue; + } + + this.contributions.push({ + title: contribution.title, + when: precondition, + command: contribution.command + }); + + } + } + }); + } + + async provideCodeActions(_model: ITextModel, _range: Range | Selection, context: modes.CodeActionContext, _token: CancellationToken): Promise { + return this.emptyCodeActionsList; + } + + public _getAdditionalMenuItems(context: modes.CodeActionContext, actions: readonly modes.CodeAction[]): modes.Command[] { + if (context.only !== CodeActionKind.Refactor.value) { + if (!actions.some(action => action.kind && CodeActionKind.Refactor.contains(new CodeActionKind(action.kind)))) { + return []; + } + } + + return this.contributions + .filter(contribution => this.contextKeyService.contextMatchesRules(contribution.when)) + .map(contribution => { + return { + id: contribution.command, + title: contribution.title + }; + }); + } +} diff --git a/src/vs/workbench/contrib/documentation/common/documentationExtensionPoint.ts b/src/vs/workbench/contrib/codeActions/common/documentationExtensionPoint.ts similarity index 65% rename from src/vs/workbench/contrib/documentation/common/documentationExtensionPoint.ts rename to src/vs/workbench/contrib/codeActions/common/documentationExtensionPoint.ts index d2099c4a8bc4..bb848f8d64d1 100644 --- a/src/vs/workbench/contrib/documentation/common/documentationExtensionPoint.ts +++ b/src/vs/workbench/contrib/codeActions/common/documentationExtensionPoint.ts @@ -11,8 +11,6 @@ export enum DocumentationExtensionPointFields { when = 'when', title = 'title', command = 'command', - view = 'view', - contents = 'contents', } export interface RefactoringDocumentationExtensionPoint { @@ -21,15 +19,8 @@ export interface RefactoringDocumentationExtensionPoint { readonly [DocumentationExtensionPointFields.command]: string; } -export interface ViewDocumentationExtensionPoint { - readonly [DocumentationExtensionPointFields.view]: string; - readonly [DocumentationExtensionPointFields.contents]: string; - readonly [DocumentationExtensionPointFields.when]: string; -} - export interface DocumentationExtensionPoint { readonly refactoring?: readonly RefactoringDocumentationExtensionPoint[]; - readonly view?: readonly ViewDocumentationExtensionPoint[]; } const documentationExtensionPointSchema = Object.freeze({ @@ -62,32 +53,6 @@ const documentationExtensionPointSchema = Object.freeze(ViewContainerExtensions.ViewsRegistry); - -export class DocumentationContribution extends Disposable implements IWorkbenchContribution, modes.CodeActionProvider { - - private codeActionContributions: ICodeActionContribution[] = []; - private readonly emptyCodeActionsList = { actions: [], dispose: () => { } }; - - private emptyViewContents = new Map(); - - constructor( - extensionPoint: IExtensionPoint, - @IContextKeyService private readonly contextKeyService: IContextKeyService, - ) { - super(); - - this._register(modes.CodeActionProviderRegistry.register('*', this)); - - extensionPoint.setHandler((points, { added, removed }) => { - for (const documentation of points) { - if (documentation.value.refactoring) { - for (const contribution of documentation.value.refactoring) { - const precondition = ContextKeyExpr.deserialize(contribution.when); - if (!precondition) { - continue; - } - - this.codeActionContributions.push({ - title: contribution.title, - when: precondition, - command: contribution.command - }); - } - } - } - - for (const documentation of removed) { - if (documentation.value.view) { - for (const contribution of documentation.value.view) { - const disposable = this.emptyViewContents.get(contribution); - - if (disposable) { - disposable.dispose(); - } - } - } - } - - for (const documentation of added) { - if (documentation.value.view) { - for (const contribution of documentation.value.view) { - const disposable = viewsRegistry.registerEmptyViewContent(contribution.view, { - content: contribution.contents, - when: ContextKeyExpr.deserialize(contribution.when) - }); - - this.emptyViewContents.set(contribution, disposable); - } - } - } - }); - } - - async provideCodeActions(_model: ITextModel, _range: Range | Selection, context: modes.CodeActionContext, _token: CancellationToken): Promise { - return this.emptyCodeActionsList; - } - - public _getAdditionalMenuItems(context: modes.CodeActionContext, actions: readonly modes.CodeAction[]): modes.Command[] { - if (context.only !== CodeActionKind.Refactor.value) { - if (!actions.some(action => action.kind && CodeActionKind.Refactor.contains(new CodeActionKind(action.kind)))) { - return []; - } - } - - return this.codeActionContributions - .filter(contribution => this.contextKeyService.contextMatchesRules(contribution.when)) - .map(contribution => { - return { - id: contribution.command, - title: contribution.title - }; - }); - } -} diff --git a/src/vs/workbench/contrib/documentation/common/documentation.contribution.ts b/src/vs/workbench/contrib/welcome/common/viewsWelcome.contribution.ts similarity index 68% rename from src/vs/workbench/contrib/documentation/common/documentation.contribution.ts rename to src/vs/workbench/contrib/welcome/common/viewsWelcome.contribution.ts index 13f09eee1872..9153e3c1a4d8 100644 --- a/src/vs/workbench/contrib/documentation/common/documentation.contribution.ts +++ b/src/vs/workbench/contrib/welcome/common/viewsWelcome.contribution.ts @@ -7,17 +7,17 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; -import { DocumentationContribution } from 'vs/workbench/contrib/documentation/common/documentationContribution'; -import { DocumentationExtensionPoint, documentationExtensionPointDescriptor } from 'vs/workbench/contrib/documentation/common/documentationExtensionPoint'; +import { ViewsWelcomeContribution } from 'vs/workbench/contrib/welcome/common/viewsWelcomeContribution'; +import { ViewsWelcomeExtensionPoint, viewsWelcomeExtensionPointDescriptor } from 'vs/workbench/contrib/welcome/common/viewsWelcomeExtensionPoint'; import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -const documentationExtensionPoint = ExtensionsRegistry.registerExtensionPoint(documentationExtensionPointDescriptor); +const extensionPoint = ExtensionsRegistry.registerExtensionPoint(viewsWelcomeExtensionPointDescriptor); class WorkbenchConfigurationContribution { constructor( @IInstantiationService instantiationService: IInstantiationService, ) { - instantiationService.createInstance(DocumentationContribution, documentationExtensionPoint); + instantiationService.createInstance(ViewsWelcomeContribution, extensionPoint); } } diff --git a/src/vs/workbench/contrib/welcome/common/viewsWelcomeContribution.ts b/src/vs/workbench/contrib/welcome/common/viewsWelcomeContribution.ts new file mode 100644 index 000000000000..d943b90176c2 --- /dev/null +++ b/src/vs/workbench/contrib/welcome/common/viewsWelcomeContribution.ts @@ -0,0 +1,46 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { IExtensionPoint } from 'vs/workbench/services/extensions/common/extensionsRegistry'; +import { ViewsWelcomeExtensionPoint, ViewWelcome } from './viewsWelcomeExtensionPoint'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { Extensions as ViewContainerExtensions, IViewsRegistry } from 'vs/workbench/common/views'; + +const viewsRegistry = Registry.as(ViewContainerExtensions.ViewsRegistry); + +export class ViewsWelcomeContribution extends Disposable implements IWorkbenchContribution { + + private viewWelcomeContents = new Map(); + + constructor(extensionPoint: IExtensionPoint) { + super(); + + extensionPoint.setHandler((_, { added, removed }) => { + for (const contribution of removed) { + for (const welcome of contribution.value) { + const disposable = this.viewWelcomeContents.get(welcome); + + if (disposable) { + disposable.dispose(); + } + } + } + + for (const contribution of added) { + for (const welcome of contribution.value) { + const disposable = viewsRegistry.registerEmptyViewContent(welcome.view, { + content: welcome.contents, + when: ContextKeyExpr.deserialize(welcome.when) + }); + + this.viewWelcomeContents.set(welcome, disposable); + } + } + }); + } +} diff --git a/src/vs/workbench/contrib/welcome/common/viewsWelcomeExtensionPoint.ts b/src/vs/workbench/contrib/welcome/common/viewsWelcomeExtensionPoint.ts new file mode 100644 index 000000000000..28a5bc02dfad --- /dev/null +++ b/src/vs/workbench/contrib/welcome/common/viewsWelcomeExtensionPoint.ts @@ -0,0 +1,53 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; + +export enum ViewsWelcomeExtensionPointFields { + view = 'view', + contents = 'contents', + when = 'when', +} + +export interface ViewWelcome { + readonly [ViewsWelcomeExtensionPointFields.view]: string; + readonly [ViewsWelcomeExtensionPointFields.contents]: string; + readonly [ViewsWelcomeExtensionPointFields.when]: string; +} + +export type ViewsWelcomeExtensionPoint = ViewWelcome[]; + +const viewsWelcomeExtensionPointSchema = Object.freeze({ + type: 'array', + description: nls.localize('contributes.viewsWelcome', "Contributed views welcome content."), + items: { + type: 'object', + description: nls.localize('contributes.viewsWelcome.view', "Contributed welcome content for a specific view."), + required: [ + ViewsWelcomeExtensionPointFields.view, + ViewsWelcomeExtensionPointFields.contents + ], + properties: { + [ViewsWelcomeExtensionPointFields.view]: { + type: 'string', + description: nls.localize('contributes.viewsWelcome.view.view', "View identifier for this welcome content."), + }, + [ViewsWelcomeExtensionPointFields.contents]: { + type: 'string', + description: nls.localize('contributes.viewsWelcome.view.contents', "Welcome content."), + }, + [ViewsWelcomeExtensionPointFields.when]: { + type: 'string', + description: nls.localize('contributes.viewsWelcome.view.when', "When clause for this welcome content."), + }, + } + } +}); + +export const viewsWelcomeExtensionPointDescriptor = { + extensionPoint: 'viewsWelcome', + jsonSchema: viewsWelcomeExtensionPointSchema +}; diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index ee4edc24c591..1e943601b665 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -272,8 +272,8 @@ import 'vs/workbench/contrib/userDataSync/browser/userDataSync.contribution'; // Code Actions import 'vs/workbench/contrib/codeActions/common/codeActions.contribution'; -// Documentation -import 'vs/workbench/contrib/documentation/common/documentation.contribution'; +// Welcome +import 'vs/workbench/contrib/welcome/common/viewsWelcome.contribution'; // Timeline import 'vs/workbench/contrib/timeline/browser/timeline.contribution'; From 61d7743219788f6375b1e8e9df1e6fb1f5973965 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 17 Feb 2020 11:29:36 +0100 Subject: [PATCH 088/109] notifications - show progress in status bar when notification is closed (part of #90274) --- src/vs/platform/progress/common/progress.ts | 36 ++-- .../parts/notifications/notificationsList.ts | 2 +- .../progress/browser/progressService.ts | 157 ++++++++++++------ 3 files changed, 124 insertions(+), 71 deletions(-) diff --git a/src/vs/platform/progress/common/progress.ts b/src/vs/platform/progress/common/progress.ts index 2b7e3b8b3856..576ae6d82fb6 100644 --- a/src/vs/platform/progress/common/progress.ts +++ b/src/vs/platform/progress/common/progress.ts @@ -17,7 +17,11 @@ export interface IProgressService { _serviceBrand: undefined; - withProgress(options: IProgressOptions | IProgressNotificationOptions | IProgressWindowOptions | IProgressCompositeOptions, task: (progress: IProgress) => Promise, onDidCancel?: (choice?: number) => void): Promise; + withProgress( + options: IProgressOptions | IProgressNotificationOptions | IProgressWindowOptions | IProgressCompositeOptions, + task: (progress: IProgress) => Promise, + onDidCancel?: (choice?: number) => void + ): Promise; } export interface IProgressIndicator { @@ -45,19 +49,19 @@ export const enum ProgressLocation { } export interface IProgressOptions { - location: ProgressLocation | string; - title?: string; - source?: string; - total?: number; - cancellable?: boolean; - buttons?: string[]; + readonly location: ProgressLocation | string; + readonly title?: string; + readonly source?: string; + readonly total?: number; + readonly cancellable?: boolean; + readonly buttons?: string[]; } export interface IProgressNotificationOptions extends IProgressOptions { readonly location: ProgressLocation.Notification; readonly primaryActions?: ReadonlyArray; readonly secondaryActions?: ReadonlyArray; - delay?: number; + readonly delay?: number; } export interface IProgressWindowOptions extends IProgressOptions { @@ -66,8 +70,8 @@ export interface IProgressWindowOptions extends IProgressOptions { } export interface IProgressCompositeOptions extends IProgressOptions { - location: ProgressLocation.Explorer | ProgressLocation.Extensions | ProgressLocation.Scm | string; - delay?: number; + readonly location: ProgressLocation.Explorer | ProgressLocation.Extensions | ProgressLocation.Scm | string; + readonly delay?: number; } export interface IProgressStep { @@ -96,20 +100,14 @@ export interface IProgress { export class Progress implements IProgress { - private _callback: (data: T) => void; private _value?: T; + get value(): T | undefined { return this._value; } - constructor(callback: (data: T) => void) { - this._callback = callback; - } - - get value(): T | undefined { - return this._value; - } + constructor(private callback: (data: T) => void) { } report(item: T) { this._value = item; - this._callback(this._value); + this.callback(this._value); } } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsList.ts b/src/vs/workbench/browser/parts/notifications/notificationsList.ts index ae4d2e69aff3..2d585698cb9b 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsList.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsList.ts @@ -181,7 +181,7 @@ export class NotificationsList extends Themable { } // Restore DOM focus if we had focus before - if (listHasDOMFocus) { + if (this.isVisible && listHasDOMFocus) { list.domFocus(); } } diff --git a/src/vs/workbench/services/progress/browser/progressService.ts b/src/vs/workbench/services/progress/browser/progressService.ts index 4a5831b56671..4ffbfbbd4c8f 100644 --- a/src/vs/workbench/services/progress/browser/progressService.ts +++ b/src/vs/workbench/services/progress/browser/progressService.ts @@ -12,9 +12,9 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { StatusbarAlignment, IStatusbarService } from 'vs/workbench/services/statusbar/common/statusbar'; import { timeout } from 'vs/base/common/async'; import { ProgressBadge, IActivityService } from 'vs/workbench/services/activity/common/activity'; -import { INotificationService, Severity, INotificationHandle, INotificationActions } from 'vs/platform/notification/common/notification'; +import { INotificationService, Severity, INotificationHandle } from 'vs/platform/notification/common/notification'; import { Action } from 'vs/base/common/actions'; -import { Event } from 'vs/base/common/event'; +import { Event, Emitter } from 'vs/base/common/event'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { Dialog } from 'vs/base/browser/ui/dialog/dialog'; @@ -143,10 +143,72 @@ export class ProgressService extends Disposable implements IProgressService { } } - private withNotificationProgress

, R = unknown>(options: IProgressNotificationOptions, callback: (progress: IProgress<{ message?: string, increment?: number }>) => P, onDidCancel?: (choice?: number) => void): P { - const toDispose = new DisposableStore(); + private withNotificationProgress

, R = unknown>(options: IProgressNotificationOptions, callback: (progress: IProgress) => P, onDidCancel?: (choice?: number) => void): P { + + const progressStateModel = new class extends Disposable { + + private readonly _onDidReport = this._register(new Emitter()); + readonly onDidReport = this._onDidReport.event; + + private readonly _onDispose = this._register(new Emitter()); + readonly onDispose = this._onDispose.event; + + private _step: IProgressStep | undefined = undefined; + get step() { return this._step; } + + private _done = false; + get done() { return this._done; } + + readonly promise: P; + + constructor() { + super(); + + this.promise = callback(this); + + this.promise.finally(() => { + this.dispose(); + }); + } + + report(step: IProgressStep): void { + this._step = step; + + this._onDidReport.fire(step); + } + + cancel(choice?: number): void { + onDidCancel?.(choice); + + this.dispose(); + } + + dispose(): void { + this._done = true; + this._onDispose.fire(); + + super.dispose(); + } + }; + + const createWindowProgress = () => { + this.withWindowProgress({ + location: ProgressLocation.Window, + title: options.title + }, progress => { + if (progressStateModel.step) { + progress.report(progressStateModel.step); + } + + const disposable = progressStateModel.onDidReport(step => progress.report(step)); + Event.once(progressStateModel.onDispose)(() => disposable.dispose()); + + return progressStateModel.promise; + }); + }; const createNotification = (message: string, increment?: number): INotificationHandle => { + const notificationDisposables = new DisposableStore(); const primaryActions = options.primaryActions ? Array.from(options.primaryActions) : []; const secondaryActions = options.secondaryActions ? Array.from(options.secondaryActions) : []; @@ -158,16 +220,11 @@ export class ProgressService extends Disposable implements IProgressService { super(`progress.button.${button}`, button, undefined, true); } - run(): Promise { - if (typeof onDidCancel === 'function') { - onDidCancel(index); - } - - return Promise.resolve(undefined); + async run(): Promise { + progressStateModel.cancel(index); } }; - - toDispose.add(buttonAction); + notificationDisposables.add(buttonAction); primaryActions.push(buttonAction); }); @@ -179,31 +236,35 @@ export class ProgressService extends Disposable implements IProgressService { super('progress.cancel', localize('cancel', "Cancel"), undefined, true); } - run(): Promise { - if (typeof onDidCancel === 'function') { - onDidCancel(); - } - - return Promise.resolve(undefined); + async run(): Promise { + progressStateModel.cancel(); } }; - toDispose.add(cancelAction); + notificationDisposables.add(cancelAction); primaryActions.push(cancelAction); } - const actions: INotificationActions = { primary: primaryActions, secondary: secondaryActions }; const handle = this.notificationService.notify({ severity: Severity.Info, message, source: options.source, - actions + actions: { primary: primaryActions, secondary: secondaryActions } }); updateProgress(handle, increment); Event.once(handle.onDidClose)(() => { - toDispose.dispose(); + + // Switch to window based progress once the notification + // is being closed even though still running and not + // cancelled. + if (!progressStateModel.done) { + createWindowProgress(); + } + + // Clear disposables + notificationDisposables.dispose(); }); return handle; @@ -218,60 +279,54 @@ export class ProgressService extends Disposable implements IProgressService { } }; - let handle: INotificationHandle | undefined; - let handleSoon: any | undefined; - + let notificationHandle: INotificationHandle | undefined; + let notificationTimeout: any | undefined; let titleAndMessage: string | undefined; // hoisted to make sure a delayed notification shows the most recent message - const updateNotification = (message?: string, increment?: number): void => { + const updateNotification = (step?: IProgressStep): void => { // full message (inital or update) - if (message && options.title) { - titleAndMessage = `${options.title}: ${message}`; // always prefix with overall title if we have it (https://github.com/Microsoft/vscode/issues/50932) + if (step?.message && options.title) { + titleAndMessage = `${options.title}: ${step.message}`; // always prefix with overall title if we have it (https://github.com/Microsoft/vscode/issues/50932) } else { - titleAndMessage = options.title || message; + titleAndMessage = options.title || step?.message; } - if (!handle && titleAndMessage) { + if (!notificationHandle && titleAndMessage) { + // create notification now or after a delay if (typeof options.delay === 'number' && options.delay > 0) { - if (typeof handleSoon !== 'number') { - handleSoon = setTimeout(() => handle = createNotification(titleAndMessage!, increment), options.delay); + if (typeof notificationTimeout !== 'number') { + notificationTimeout = setTimeout(() => notificationHandle = createNotification(titleAndMessage!, step?.increment), options.delay); } } else { - handle = createNotification(titleAndMessage, increment); + notificationHandle = createNotification(titleAndMessage, step?.increment); } } - if (handle) { + if (notificationHandle) { if (titleAndMessage) { - handle.updateMessage(titleAndMessage); + notificationHandle.updateMessage(titleAndMessage); } - if (typeof increment === 'number') { - updateProgress(handle, increment); + + if (typeof step?.increment === 'number') { + updateProgress(notificationHandle, step.increment); } } }; // Show initially - updateNotification(); - - // Update based on progress - const promise = callback({ - report: progress => { - updateNotification(progress.message, progress.increment); - } - }); + updateNotification(progressStateModel.step); + const listener = progressStateModel.onDidReport(step => updateNotification(step)); + Event.once(progressStateModel.onDispose)(() => listener.dispose()); // Show progress for at least 800ms and then hide once done or canceled - Promise.all([timeout(800), promise]).finally(() => { - clearTimeout(handleSoon); - if (handle) { - handle.close(); - } + Promise.all([timeout(800), progressStateModel.promise]).finally(() => { + clearTimeout(notificationTimeout); + notificationHandle?.close(); }); - return promise; + return progressStateModel.promise; } private withViewletProgress

, R = unknown>(viewletId: string, task: (progress: IProgress) => P, options: IProgressCompositeOptions): P { From 71cd13766854103d765594bb921c55e10c7587fc Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 17 Feb 2020 11:34:45 +0100 Subject: [PATCH 089/109] views: empty -> welcome --- .../browser/parts/views/media/views.css | 8 +-- .../browser/parts/views/viewPaneContainer.ts | 72 +++++++++---------- src/vs/workbench/common/views.ts | 28 ++++---- .../contrib/scm/browser/repositoryPane.ts | 2 +- .../contrib/scm/browser/scmViewlet.ts | 6 +- .../common/viewsWelcomeContribution.ts | 2 +- 6 files changed, 59 insertions(+), 59 deletions(-) diff --git a/src/vs/workbench/browser/parts/views/media/views.css b/src/vs/workbench/browser/parts/views/media/views.css index 2e4de06c27b2..bd12bd60f9b9 100644 --- a/src/vs/workbench/browser/parts/views/media/views.css +++ b/src/vs/workbench/browser/parts/views/media/views.css @@ -71,7 +71,7 @@ display: none; } -.monaco-workbench .pane > .pane-body > .empty-view { +.monaco-workbench .pane > .pane-body > .welcome-view { width: 100%; height: 100%; padding: 0 20px 0 20px; @@ -79,12 +79,12 @@ box-sizing: border-box; } -.monaco-workbench .pane > .pane-body:not(.empty) > .empty-view, -.monaco-workbench .pane > .pane-body.empty > :not(.empty-view) { +.monaco-workbench .pane > .pane-body:not(.welcome) > .welcome-view, +.monaco-workbench .pane > .pane-body.welcome > :not(.welcome-view) { display: none; } -.monaco-workbench .pane > .pane-body > .empty-view .monaco-button { +.monaco-workbench .pane > .pane-body > .welcome-view .monaco-button { max-width: 260px; margin-left: auto; margin-right: auto; diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index d380b02c9e8f..bd0b2276d371 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -65,18 +65,18 @@ interface IItem { visible: boolean; } -class EmptyViewsController { +class ViewWelcomeController { private _onDidChange = new Emitter(); readonly onDidChange = this._onDidChange.event; - private placeholderItem: IItem | undefined; + private defaultItem: IItem | undefined; private items: IItem[] = []; - get viewContentDescriptors(): IViewContentDescriptor[] { + get contents(): IViewContentDescriptor[] { const visibleItems = this.items.filter(v => v.visible); - if (visibleItems.length === 0 && this.placeholderItem) { - return [this.placeholderItem.descriptor]; + if (visibleItems.length === 0 && this.defaultItem) { + return [this.defaultItem.descriptor]; } return visibleItems.map(v => v.descriptor); @@ -93,18 +93,18 @@ class EmptyViewsController { this.disposables.add(this.contextKeyService); contextKeyService.onDidChangeContext(this.onDidChangeContext, this, this.disposables); - Event.filter(viewsRegistry.onDidChangeEmptyViewContent, id => id === this.id)(this.onDidChangeEmptyViewContent, this, this.disposables); - this.onDidChangeEmptyViewContent(); + Event.filter(viewsRegistry.onDidChangeViewWelcomeContent, id => id === this.id)(this.onDidChangeViewWelcomeContent, this, this.disposables); + this.onDidChangeViewWelcomeContent(); } - private onDidChangeEmptyViewContent(): void { - const contentDescriptors = viewsRegistry.getEmptyViewContent(this.id); + private onDidChangeViewWelcomeContent(): void { + const descriptors = viewsRegistry.getViewWelcomeContent(this.id); this.items = []; - for (const descriptor of contentDescriptors) { - if (descriptor.when === 'placeholder') { - this.placeholderItem = { descriptor, visible: true }; + for (const descriptor of descriptors) { + if (descriptor.when === 'default') { + this.defaultItem = { descriptor, visible: true }; } else { const visible = descriptor.when ? this.contextKeyService.contextMatchesRules(descriptor.when) : true; this.items.push({ descriptor, visible }); @@ -118,7 +118,7 @@ class EmptyViewsController { let didChange = false; for (const item of this.items) { - if (!item.descriptor.when || item.descriptor.when === 'placeholder') { + if (!item.descriptor.when || item.descriptor.when === 'default') { continue; } @@ -158,8 +158,8 @@ export abstract class ViewPane extends Pane implements IView { protected _onDidChangeTitleArea = this._register(new Emitter()); readonly onDidChangeTitleArea: Event = this._onDidChangeTitleArea.event; - protected _onDidChangeEmptyState = this._register(new Emitter()); - readonly onDidChangeEmptyState: Event = this._onDidChangeEmptyState.event; + protected _onDidChangeViewWelcomeState = this._register(new Emitter()); + readonly onDidChangeViewWelcomeState: Event = this._onDidChangeViewWelcomeState.event; private focusedViewContextKey: IContextKey; @@ -177,9 +177,9 @@ export abstract class ViewPane extends Pane implements IView { protected twistiesContainer?: HTMLElement; private bodyContainer!: HTMLElement; - private emptyViewContainer!: HTMLElement; - private emptyViewDisposable: IDisposable = Disposable.None; - private emptyViewsController: EmptyViewsController; + private viewWelcomeContainer!: HTMLElement; + private viewWelcomeDisposable: IDisposable = Disposable.None; + private viewWelcomeController: ViewWelcomeController; constructor( options: IViewPaneOptions, @@ -203,7 +203,7 @@ export abstract class ViewPane extends Pane implements IView { this.menuActions = this._register(instantiationService.createInstance(ViewMenuActions, this.id, options.titleMenuId || MenuId.ViewTitle, MenuId.ViewTitleContext)); this._register(this.menuActions.onDidChangeTitle(() => this.updateActions())); - this.emptyViewsController = new EmptyViewsController(this.id, contextKeyService); + this.viewWelcomeController = new ViewWelcomeController(this.id, contextKeyService); } setVisible(visible: boolean): void { @@ -291,11 +291,11 @@ export abstract class ViewPane extends Pane implements IView { protected renderBody(container: HTMLElement): void { this.bodyContainer = container; - this.emptyViewContainer = append(container, $('.empty-view', { tabIndex: 0 })); + this.viewWelcomeContainer = append(container, $('.welcome-view', { tabIndex: 0 })); - const onEmptyViewContentChange = Event.any(this.emptyViewsController.onDidChange, this.onDidChangeEmptyState); - this._register(onEmptyViewContentChange(this.updateEmptyContents, this)); - this.updateEmptyContents(); + const onViewWelcomeChange = Event.any(this.viewWelcomeController.onDidChange, this.onDidChangeViewWelcomeState); + this._register(onViewWelcomeChange(this.updateViewWelcome, this)); + this.updateViewWelcome(); } protected layoutBody(height: number, width: number): void { @@ -368,26 +368,26 @@ export abstract class ViewPane extends Pane implements IView { // Subclasses to implement for saving state } - private updateEmptyContents(): void { - this.emptyViewDisposable.dispose(); + private updateViewWelcome(): void { + this.viewWelcomeDisposable.dispose(); - if (!this.isEmpty()) { - removeClass(this.bodyContainer, 'empty'); - this.emptyViewContainer.innerHTML = ''; + if (!this.shouldShowWelcome()) { + removeClass(this.bodyContainer, 'welcome'); + this.viewWelcomeContainer.innerHTML = ''; return; } - const contents = this.emptyViewsController.viewContentDescriptors; + const contents = this.viewWelcomeController.contents; if (contents.length === 0) { - removeClass(this.bodyContainer, 'empty'); - this.emptyViewContainer.innerHTML = ''; + removeClass(this.bodyContainer, 'welcome'); + this.viewWelcomeContainer.innerHTML = ''; return; } const disposables = new DisposableStore(); - addClass(this.bodyContainer, 'empty'); - this.emptyViewContainer.innerHTML = ''; + addClass(this.bodyContainer, 'welcome'); + this.viewWelcomeContainer.innerHTML = ''; for (const { content } of contents) { const lines = content.split('\n'); @@ -399,7 +399,7 @@ export abstract class ViewPane extends Pane implements IView { continue; } - const p = append(this.emptyViewContainer, $('p')); + const p = append(this.viewWelcomeContainer, $('p')); const linkedText = parseLinkedText(line); for (const node of linkedText) { @@ -421,10 +421,10 @@ export abstract class ViewPane extends Pane implements IView { } } - this.emptyViewDisposable = disposables; + this.viewWelcomeDisposable = disposables; } - isEmpty(): boolean { + shouldShowWelcome(): boolean { return false; } } diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index d1c4eba42949..cb64eeb09338 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -213,7 +213,7 @@ export interface IViewDescriptorCollection extends IDisposable { export interface IViewContentDescriptor { readonly content: string; - readonly when?: ContextKeyExpr | 'placeholder'; + readonly when?: ContextKeyExpr | 'default'; } export interface IViewsRegistry { @@ -236,9 +236,9 @@ export interface IViewsRegistry { getViewContainer(id: string): ViewContainer | null; - readonly onDidChangeEmptyViewContent: Event; - registerEmptyViewContent(id: string, viewContent: IViewContentDescriptor): IDisposable; - getEmptyViewContent(id: string): IViewContentDescriptor[]; + readonly onDidChangeViewWelcomeContent: Event; + registerViewWelcomeContent(id: string, viewContent: IViewContentDescriptor): IDisposable; + getViewWelcomeContent(id: string): IViewContentDescriptor[]; } function compareViewContentDescriptors(a: IViewContentDescriptor, b: IViewContentDescriptor): number { @@ -256,12 +256,12 @@ class ViewsRegistry extends Disposable implements IViewsRegistry { private readonly _onDidChangeContainer: Emitter<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }> = this._register(new Emitter<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }>()); readonly onDidChangeContainer: Event<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }> = this._onDidChangeContainer.event; - private readonly _onDidChangeEmptyViewContent: Emitter = this._register(new Emitter()); - readonly onDidChangeEmptyViewContent: Event = this._onDidChangeEmptyViewContent.event; + private readonly _onDidChangeViewWelcomeContent: Emitter = this._register(new Emitter()); + readonly onDidChangeViewWelcomeContent: Event = this._onDidChangeViewWelcomeContent.event; private _viewContainers: ViewContainer[] = []; private _views: Map = new Map(); - private _emptyViewContents = new SetMap(); + private _viewWelcomeContents = new SetMap(); registerViews(views: IViewDescriptor[], viewContainer: ViewContainer): void { this.addViews(views, viewContainer); @@ -311,20 +311,20 @@ class ViewsRegistry extends Disposable implements IViewsRegistry { return null; } - registerEmptyViewContent(id: string, viewContent: IViewContentDescriptor): IDisposable { - this._emptyViewContents.add(id, viewContent); - this._onDidChangeEmptyViewContent.fire(id); + registerViewWelcomeContent(id: string, viewContent: IViewContentDescriptor): IDisposable { + this._viewWelcomeContents.add(id, viewContent); + this._onDidChangeViewWelcomeContent.fire(id); return toDisposable(() => { - this._emptyViewContents.delete(id, viewContent); - this._onDidChangeEmptyViewContent.fire(id); + this._viewWelcomeContents.delete(id, viewContent); + this._onDidChangeViewWelcomeContent.fire(id); }); } - getEmptyViewContent(id: string): IViewContentDescriptor[] { + getViewWelcomeContent(id: string): IViewContentDescriptor[] { const result: IViewContentDescriptor[] = []; result.sort(compareViewContentDescriptors); - this._emptyViewContents.forEach(id, descriptor => result.push(descriptor)); + this._viewWelcomeContents.forEach(id, descriptor => result.push(descriptor)); return result; } diff --git a/src/vs/workbench/contrib/scm/browser/repositoryPane.ts b/src/vs/workbench/contrib/scm/browser/repositoryPane.ts index 3f65b434ea8c..ab9c041d1259 100644 --- a/src/vs/workbench/contrib/scm/browser/repositoryPane.ts +++ b/src/vs/workbench/contrib/scm/browser/repositoryPane.ts @@ -615,7 +615,7 @@ export class RepositoryPane extends ViewPane { protected contextKeyService: IContextKeyService; private commitTemplate = ''; - isEmpty() { return true; } + shouldShowWelcome() { return true; } constructor( readonly repository: ISCMRepository, diff --git a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts index 26a7fd69aab8..035f4617e542 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts @@ -60,7 +60,7 @@ export class EmptyPane extends ViewPane { super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); } - isEmpty(): boolean { + shouldShowWelcome(): boolean { return true; } } @@ -131,9 +131,9 @@ export class SCMViewPaneContainer extends ViewPaneContainer implements IViewMode const viewsRegistry = Registry.as(Extensions.ViewsRegistry); - viewsRegistry.registerEmptyViewContent(EmptyPane.ID, { + viewsRegistry.registerViewWelcomeContent(EmptyPane.ID, { content: localize('no open repo', "No source control providers registered."), - when: 'placeholder' + when: 'default' }); viewsRegistry.registerViews([new EmptyPaneDescriptor()], this.viewContainer); diff --git a/src/vs/workbench/contrib/welcome/common/viewsWelcomeContribution.ts b/src/vs/workbench/contrib/welcome/common/viewsWelcomeContribution.ts index d943b90176c2..c1bde25446aa 100644 --- a/src/vs/workbench/contrib/welcome/common/viewsWelcomeContribution.ts +++ b/src/vs/workbench/contrib/welcome/common/viewsWelcomeContribution.ts @@ -33,7 +33,7 @@ export class ViewsWelcomeContribution extends Disposable implements IWorkbenchCo for (const contribution of added) { for (const welcome of contribution.value) { - const disposable = viewsRegistry.registerEmptyViewContent(welcome.view, { + const disposable = viewsRegistry.registerViewWelcomeContent(welcome.view, { content: welcome.contents, when: ContextKeyExpr.deserialize(welcome.when) }); From ae74703f28124a5f66064bdf1a2d10ef2d1b064c Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 17 Feb 2020 11:41:42 +0100 Subject: [PATCH 090/109] notifications - use disposable for handle --- src/vs/workbench/common/notifications.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/common/notifications.ts b/src/vs/workbench/common/notifications.ts index 01d7271a3b9a..433adb6ed20f 100644 --- a/src/vs/workbench/common/notifications.ts +++ b/src/vs/workbench/common/notifications.ts @@ -88,19 +88,22 @@ export interface IStatusMessageChangeEvent { kind: StatusMessageChangeType; } -export class NotificationHandle implements INotificationHandle { +export class NotificationHandle extends Disposable implements INotificationHandle { - private readonly _onDidClose: Emitter = new Emitter(); - readonly onDidClose: Event = this._onDidClose.event; + private readonly _onDidClose = this._register(new Emitter()); + readonly onDidClose = this._onDidClose.event; + + constructor(private readonly item: INotificationViewItem, private readonly onClose: (item: INotificationViewItem) => void) { + super(); - constructor(private readonly item: INotificationViewItem, private readonly closeItem: (item: INotificationViewItem) => void) { this.registerListeners(); } private registerListeners(): void { Event.once(this.item.onDidClose)(() => { this._onDidClose.fire(); - this._onDidClose.dispose(); + + this.dispose(); }); } @@ -121,8 +124,9 @@ export class NotificationHandle implements INotificationHandle { } close(): void { - this.closeItem(this.item); - this._onDidClose.dispose(); + this.onClose(this.item); + + this.dispose(); } } From c6e0aaa322323eb38f38f3d662b887594f606ee9 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 17 Feb 2020 11:56:11 +0100 Subject: [PATCH 091/109] views welcome: check for proposed api flag --- .../welcome/common/viewsWelcomeContribution.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/welcome/common/viewsWelcomeContribution.ts b/src/vs/workbench/contrib/welcome/common/viewsWelcomeContribution.ts index c1bde25446aa..51fb3194e959 100644 --- a/src/vs/workbench/contrib/welcome/common/viewsWelcomeContribution.ts +++ b/src/vs/workbench/contrib/welcome/common/viewsWelcomeContribution.ts @@ -7,9 +7,10 @@ import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IExtensionPoint } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { ViewsWelcomeExtensionPoint, ViewWelcome } from './viewsWelcomeExtensionPoint'; +import { ViewsWelcomeExtensionPoint, ViewWelcome, viewsWelcomeExtensionPointDescriptor } from './viewsWelcomeExtensionPoint'; import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as ViewContainerExtensions, IViewsRegistry } from 'vs/workbench/common/views'; +import { localize } from 'vs/nls'; const viewsRegistry = Registry.as(ViewContainerExtensions.ViewsRegistry); @@ -22,6 +23,11 @@ export class ViewsWelcomeContribution extends Disposable implements IWorkbenchCo extensionPoint.setHandler((_, { added, removed }) => { for (const contribution of removed) { + // Proposed API check + if (!contribution.description.enableProposedApi) { + continue; + } + for (const welcome of contribution.value) { const disposable = this.viewWelcomeContents.get(welcome); @@ -32,6 +38,12 @@ export class ViewsWelcomeContribution extends Disposable implements IWorkbenchCo } for (const contribution of added) { + // Proposed API check + if (!contribution.description.enableProposedApi) { + contribution.collector.error(localize('proposedAPI.invalid', "The '{0}' contribution is a proposed API and is only available when running out of dev or with the following command line switch: --enable-proposed-api {1}", viewsWelcomeExtensionPointDescriptor.extensionPoint, contribution.description.identifier.value)); + continue; + } + for (const welcome of contribution.value) { const disposable = viewsRegistry.registerViewWelcomeContent(welcome.view, { content: welcome.contents, From 12215af89154f3b3eaeb92e7dc77fa7d024b47de Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 17 Feb 2020 12:09:40 +0100 Subject: [PATCH 092/109] notifications - change event names to follow conventions --- .../notifications/notificationsAlerts.ts | 8 +-- .../notifications/notificationsCenter.ts | 4 +- .../notifications/notificationsStatus.ts | 8 +-- .../notifications/notificationsToasts.ts | 10 ++-- .../notifications/notificationsViewer.ts | 2 +- src/vs/workbench/common/notifications.ts | 58 +++++++++---------- .../test/common/notifications.test.ts | 14 ++--- 7 files changed, 52 insertions(+), 52 deletions(-) diff --git a/src/vs/workbench/browser/parts/notifications/notificationsAlerts.ts b/src/vs/workbench/browser/parts/notifications/notificationsAlerts.ts index 94a5a660b43d..fae7f87f7cbd 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsAlerts.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsAlerts.ts @@ -23,10 +23,10 @@ export class NotificationsAlerts extends Disposable { } private registerListeners(): void { - this._register(this.model.onDidNotificationChange(e => this.onDidNotificationChange(e))); + this._register(this.model.onDidChangeNotification(e => this.onDidChangeNotification(e))); } - private onDidNotificationChange(e: INotificationChangeEvent): void { + private onDidChangeNotification(e: INotificationChangeEvent): void { if (e.kind === NotificationChangeType.ADD) { // ARIA alert for screen readers @@ -46,7 +46,7 @@ export class NotificationsAlerts extends Disposable { private triggerAriaAlert(notifiation: INotificationViewItem): void { // Trigger the alert again whenever the label changes - const listener = notifiation.onDidLabelChange(e => { + const listener = notifiation.onDidChangeLabel(e => { if (e.kind === NotificationViewItemLabelKind.MESSAGE) { this.doTriggerAriaAlert(notifiation); } @@ -69,4 +69,4 @@ export class NotificationsAlerts extends Disposable { alert(alertText); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts index 6d27e856dbba..0b4cbe772643 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts @@ -58,7 +58,7 @@ export class NotificationsCenter extends Themable { } private registerListeners(): void { - this._register(this.model.onDidNotificationChange(e => this.onDidNotificationChange(e))); + this._register(this.model.onDidChangeNotification(e => this.onDidChangeNotification(e))); this._register(this.layoutService.onLayout(dimension => this.layout(dimension))); } @@ -167,7 +167,7 @@ export class NotificationsCenter extends Themable { return keybinding ? keybinding.getLabel() : null; } - private onDidNotificationChange(e: INotificationChangeEvent): void { + private onDidChangeNotification(e: INotificationChangeEvent): void { if (!this._isVisible) { return; // only if visible } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts b/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts index 97c9af2a6221..3545ab9c02b6 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts @@ -34,11 +34,11 @@ export class NotificationsStatus extends Disposable { } private registerListeners(): void { - this._register(this.model.onDidNotificationChange(e => this.onDidNotificationChange(e))); - this._register(this.model.onDidStatusMessageChange(e => this.onDidStatusMessageChange(e))); + this._register(this.model.onDidChangeNotification(e => this.onDidChangeNotification(e))); + this._register(this.model.onDidChangeStatusMessage(e => this.onDidChangeStatusMessage(e))); } - private onDidNotificationChange(e: INotificationChangeEvent): void { + private onDidChangeNotification(e: INotificationChangeEvent): void { if (this.isNotificationsCenterVisible) { return; // no change if notification center is visible } @@ -101,7 +101,7 @@ export class NotificationsStatus extends Disposable { } } - private onDidStatusMessageChange(e: IStatusMessageChangeEvent): void { + private onDidChangeStatusMessage(e: IStatusMessageChangeEvent): void { const statusItem = e.item; switch (e.kind) { diff --git a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts index ee6bbffc2833..9461b4887ac7 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts @@ -90,11 +90,11 @@ export class NotificationsToasts extends Themable { this.model.notifications.forEach(notification => this.addToast(notification)); // Update toasts on notification changes - this._register(this.model.onDidNotificationChange(e => this.onDidNotificationChange(e))); + this._register(this.model.onDidChangeNotification(e => this.onDidChangeNotification(e))); }); // Filter - this._register(this.model.onDidFilterChange(filter => { + this._register(this.model.onDidChangeFilter(filter => { if (filter === NotificationsFilter.SILENT || filter === NotificationsFilter.ERROR) { this.hide(); } @@ -114,7 +114,7 @@ export class NotificationsToasts extends Themable { ]); } - private onDidNotificationChange(e: INotificationChangeEvent): void { + private onDidChangeNotification(e: INotificationChangeEvent): void { switch (e.kind) { case NotificationChangeType.ADD: return this.addToast(e.item); @@ -194,12 +194,12 @@ export class NotificationsToasts extends Themable { this.layoutContainer(maxDimensions.height); // Update when item height changes due to expansion - itemDisposables.add(item.onDidExpansionChange(() => { + itemDisposables.add(item.onDidChangeExpansion(() => { notificationList.updateNotificationsList(0, 1, [item]); })); // Update when item height potentially changes due to label changes - itemDisposables.add(item.onDidLabelChange(e => { + itemDisposables.add(item.onDidChangeLabel(e => { if (!item.expanded) { return; // dynamic height only applies to expanded notifications } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts index a41db2f2ee11..318307a84774 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts @@ -345,7 +345,7 @@ export class NotificationTemplateRenderer extends Disposable { this.renderProgress(notification); // Label Change Events - this.inputDisposables.add(notification.onDidLabelChange(event => { + this.inputDisposables.add(notification.onDidChangeLabel(event => { switch (event.kind) { case NotificationViewItemLabelKind.SEVERITY: this.renderSeverity(notification); diff --git a/src/vs/workbench/common/notifications.ts b/src/vs/workbench/common/notifications.ts index 433adb6ed20f..ff932b00bd82 100644 --- a/src/vs/workbench/common/notifications.ts +++ b/src/vs/workbench/common/notifications.ts @@ -20,8 +20,8 @@ export interface INotificationsModel { readonly notifications: INotificationViewItem[]; - readonly onDidNotificationChange: Event; - readonly onDidFilterChange: Event; + readonly onDidChangeNotification: Event; + readonly onDidChangeFilter: Event; addNotification(notification: INotification): INotificationHandle; @@ -34,7 +34,7 @@ export interface INotificationsModel { readonly statusMessage: IStatusMessageViewItem | undefined; - readonly onDidStatusMessageChange: Event; + readonly onDidChangeStatusMessage: Event; showStatusMessage(message: NotificationMessage, options?: IStatusMessageOptions): IDisposable; @@ -134,14 +134,14 @@ export class NotificationsModel extends Disposable implements INotificationsMode private static readonly NO_OP_NOTIFICATION = new NoOpNotification(); - private readonly _onDidNotificationChange = this._register(new Emitter()); - readonly onDidNotificationChange = this._onDidNotificationChange.event; + private readonly _onDidChangeNotification = this._register(new Emitter()); + readonly onDidChangeNotification = this._onDidChangeNotification.event; - private readonly _onDidStatusMessageChange = this._register(new Emitter()); - readonly onDidStatusMessageChange = this._onDidStatusMessageChange.event; + private readonly _onDidChangeStatusMessage = this._register(new Emitter()); + readonly onDidChangeStatusMessage = this._onDidChangeStatusMessage.event; - private readonly _onDidFilterChange = this._register(new Emitter()); - readonly onDidFilterChange = this._onDidFilterChange.event; + private readonly _onDidChangeFilter = this._register(new Emitter()); + readonly onDidChangeFilter = this._onDidChangeFilter.event; private readonly _notifications: INotificationViewItem[] = []; get notifications(): INotificationViewItem[] { return this._notifications; } @@ -154,7 +154,7 @@ export class NotificationsModel extends Disposable implements INotificationsMode setFilter(filter: NotificationsFilter): void { this.filter = filter; - this._onDidFilterChange.fire(filter); + this._onDidChangeFilter.fire(filter); } addNotification(notification: INotification): INotificationHandle { @@ -173,7 +173,7 @@ export class NotificationsModel extends Disposable implements INotificationsMode this._notifications.splice(0, 0, item); // Events - this._onDidNotificationChange.fire({ item, index: 0, kind: NotificationChangeType.ADD }); + this._onDidChangeNotification.fire({ item, index: 0, kind: NotificationChangeType.ADD }); // Wrap into handle return new NotificationHandle(item, item => this.onClose(item)); @@ -202,13 +202,13 @@ export class NotificationsModel extends Disposable implements INotificationsMode const onItemChangeEvent = () => { const index = this._notifications.indexOf(item); if (index >= 0) { - this._onDidNotificationChange.fire({ item, index, kind: NotificationChangeType.CHANGE }); + this._onDidChangeNotification.fire({ item, index, kind: NotificationChangeType.CHANGE }); } }; - const itemExpansionChangeListener = item.onDidExpansionChange(() => onItemChangeEvent()); + const itemExpansionChangeListener = item.onDidChangeExpansion(() => onItemChangeEvent()); - const itemLabelChangeListener = item.onDidLabelChange(e => { + const itemLabelChangeListener = item.onDidChangeLabel(e => { // a label change in the area of actions or the message is a change that potentially has an impact // on the size of the notification and as such we emit a change event so that viewers can redraw if (e.kind === NotificationViewItemLabelKind.ACTIONS || e.kind === NotificationViewItemLabelKind.MESSAGE) { @@ -223,7 +223,7 @@ export class NotificationsModel extends Disposable implements INotificationsMode const index = this._notifications.indexOf(item); if (index >= 0) { this._notifications.splice(index, 1); - this._onDidNotificationChange.fire({ item, index, kind: NotificationChangeType.REMOVE }); + this._onDidChangeNotification.fire({ item, index, kind: NotificationChangeType.REMOVE }); } }); @@ -238,14 +238,14 @@ export class NotificationsModel extends Disposable implements INotificationsMode // Remember as current status message and fire events this._statusMessage = item; - this._onDidStatusMessageChange.fire({ kind: StatusMessageChangeType.ADD, item }); + this._onDidChangeStatusMessage.fire({ kind: StatusMessageChangeType.ADD, item }); return toDisposable(() => { // Only reset status message if the item is still the one we had remembered if (this._statusMessage === item) { this._statusMessage = undefined; - this._onDidStatusMessageChange.fire({ kind: StatusMessageChangeType.REMOVE, item }); + this._onDidChangeStatusMessage.fire({ kind: StatusMessageChangeType.REMOVE, item }); } }); } @@ -263,9 +263,9 @@ export interface INotificationViewItem { readonly expanded: boolean; readonly canCollapse: boolean; - readonly onDidExpansionChange: Event; + readonly onDidChangeExpansion: Event; readonly onDidClose: Event; - readonly onDidLabelChange: Event; + readonly onDidChangeLabel: Event; expand(): void; collapse(skipEvents?: boolean): void; @@ -410,14 +410,14 @@ export class NotificationViewItem extends Disposable implements INotificationVie private _actions: INotificationActions | undefined; private _progress: NotificationViewItemProgress | undefined; - private readonly _onDidExpansionChange = this._register(new Emitter()); - readonly onDidExpansionChange = this._onDidExpansionChange.event; + private readonly _onDidChangeExpansion = this._register(new Emitter()); + readonly onDidChangeExpansion = this._onDidChangeExpansion.event; private readonly _onDidClose = this._register(new Emitter()); readonly onDidClose = this._onDidClose.event; - private readonly _onDidLabelChange = this._register(new Emitter()); - readonly onDidLabelChange = this._onDidLabelChange.event; + private readonly _onDidChangeLabel = this._register(new Emitter()); + readonly onDidChangeLabel = this._onDidChangeLabel.event; static create(notification: INotification, filter: NotificationsFilter = NotificationsFilter.OFF): INotificationViewItem | undefined { if (!notification || !notification.message || isPromiseCanceledError(notification.message)) { @@ -566,7 +566,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie get progress(): INotificationViewItemProgress { if (!this._progress) { this._progress = this._register(new NotificationViewItemProgress()); - this._register(this._progress.onDidChange(() => this._onDidLabelChange.fire({ kind: NotificationViewItemLabelKind.PROGRESS }))); + this._register(this._progress.onDidChange(() => this._onDidChangeLabel.fire({ kind: NotificationViewItemLabelKind.PROGRESS }))); } return this._progress; @@ -586,7 +586,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie updateSeverity(severity: Severity): void { this._severity = severity; - this._onDidLabelChange.fire({ kind: NotificationViewItemLabelKind.SEVERITY }); + this._onDidChangeLabel.fire({ kind: NotificationViewItemLabelKind.SEVERITY }); } updateMessage(input: NotificationMessage): void { @@ -596,13 +596,13 @@ export class NotificationViewItem extends Disposable implements INotificationVie } this._message = message; - this._onDidLabelChange.fire({ kind: NotificationViewItemLabelKind.MESSAGE }); + this._onDidChangeLabel.fire({ kind: NotificationViewItemLabelKind.MESSAGE }); } updateActions(actions?: INotificationActions): void { this.setActions(actions); - this._onDidLabelChange.fire({ kind: NotificationViewItemLabelKind.ACTIONS }); + this._onDidChangeLabel.fire({ kind: NotificationViewItemLabelKind.ACTIONS }); } expand(): void { @@ -611,7 +611,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie } this._expanded = true; - this._onDidExpansionChange.fire(); + this._onDidChangeExpansion.fire(); } collapse(skipEvents?: boolean): void { @@ -622,7 +622,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie this._expanded = false; if (!skipEvents) { - this._onDidExpansionChange.fire(); + this._onDidChangeExpansion.fire(); } } diff --git a/src/vs/workbench/test/common/notifications.test.ts b/src/vs/workbench/test/common/notifications.test.ts index b8a5e1b1ee1a..b95b7d7b1eb0 100644 --- a/src/vs/workbench/test/common/notifications.test.ts +++ b/src/vs/workbench/test/common/notifications.test.ts @@ -41,7 +41,7 @@ suite('Notifications', () => { // Events let called = 0; - item1.onDidExpansionChange(() => { + item1.onDidChangeExpansion(() => { called++; }); @@ -53,7 +53,7 @@ suite('Notifications', () => { assert.equal(called, 2); called = 0; - item1.onDidLabelChange(e => { + item1.onDidChangeLabel(e => { if (e.kind === NotificationViewItemLabelKind.PROGRESS) { called++; } @@ -65,7 +65,7 @@ suite('Notifications', () => { assert.equal(called, 2); called = 0; - item1.onDidLabelChange(e => { + item1.onDidChangeLabel(e => { if (e.kind === NotificationViewItemLabelKind.MESSAGE) { called++; } @@ -74,7 +74,7 @@ suite('Notifications', () => { item1.updateMessage('message update'); called = 0; - item1.onDidLabelChange(e => { + item1.onDidChangeLabel(e => { if (e.kind === NotificationViewItemLabelKind.SEVERITY) { called++; } @@ -83,7 +83,7 @@ suite('Notifications', () => { item1.updateSeverity(Severity.Error); called = 0; - item1.onDidLabelChange(e => { + item1.onDidChangeLabel(e => { if (e.kind === NotificationViewItemLabelKind.ACTIONS) { called++; } @@ -146,12 +146,12 @@ suite('Notifications', () => { const model = new NotificationsModel(); let lastNotificationEvent!: INotificationChangeEvent; - model.onDidNotificationChange(e => { + model.onDidChangeNotification(e => { lastNotificationEvent = e; }); let lastStatusMessageEvent!: IStatusMessageChangeEvent; - model.onDidStatusMessageChange(e => { + model.onDidChangeStatusMessage(e => { lastStatusMessageEvent = e; }); From 866dffcb1d78df57a417979b529400a9c0a53eaa Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 17 Feb 2020 12:20:31 +0100 Subject: [PATCH 093/109] Add minimap.mode: "contain" --- .../browser/viewParts/minimap/minimap.ts | 16 +- src/vs/editor/common/config/editorOptions.ts | 62 ++++--- .../viewLayout/editorLayoutProvider.test.ts | 171 ++++++++++++++++-- src/vs/monaco.d.ts | 10 +- 4 files changed, 212 insertions(+), 47 deletions(-) diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index 8301788bdc99..adc23678bfe0 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -46,7 +46,9 @@ class MinimapOptions { public readonly renderMinimap: RenderMinimap; - public readonly entireDocument: boolean; + public readonly mode: 'actual' | 'cover' | 'contain'; + + public readonly minimapHeightIsEditorHeight: boolean; public readonly scrollBeyondLastLine: boolean; @@ -104,7 +106,8 @@ class MinimapOptions { const minimapOpts = options.get(EditorOption.minimap); this.renderMinimap = layoutInfo.renderMinimap | 0; - this.entireDocument = minimapOpts.entireDocument; + this.mode = minimapOpts.mode; + this.minimapHeightIsEditorHeight = layoutInfo.minimapHeightIsEditorHeight; this.scrollBeyondLastLine = options.get(EditorOption.scrollBeyondLastLine); this.showSlider = minimapOpts.showSlider; this.pixelRatio = pixelRatio; @@ -137,7 +140,8 @@ class MinimapOptions { public equals(other: MinimapOptions): boolean { return (this.renderMinimap === other.renderMinimap - && this.entireDocument === other.entireDocument + && this.mode === other.mode + && this.minimapHeightIsEditorHeight === other.minimapHeightIsEditorHeight && this.scrollBeyondLastLine === other.scrollBeyondLastLine && this.showSlider === other.showSlider && this.pixelRatio === other.pixelRatio @@ -237,7 +241,7 @@ class MinimapLayout { const minimapLinesFitting = Math.floor(options.canvasInnerHeight / minimapLineHeight); const lineHeight = options.lineHeight; - if (options.entireDocument) { + if (options.minimapHeightIsEditorHeight) { const logicalScrollHeight = ( realLineCount * options.lineHeight + (options.scrollBeyondLastLine ? viewportHeight - options.lineHeight : 0) @@ -291,7 +295,7 @@ class MinimapLayout { let extraLinesAtTheBottom = 0; if (options.scrollBeyondLastLine) { const expectedViewportLineCount = viewportHeight / lineHeight; - extraLinesAtTheBottom = expectedViewportLineCount; + extraLinesAtTheBottom = expectedViewportLineCount - 1; } if (minimapLinesFitting >= lineCount + extraLinesAtTheBottom) { // All lines fit in the minimap @@ -531,7 +535,7 @@ class MinimapSamplingState { const pixelRatio = options.get(EditorOption.pixelRatio); const lineHeight = options.get(EditorOption.lineHeight); const scrollBeyondLastLine = options.get(EditorOption.scrollBeyondLastLine); - const { minimapLineCount } = EditorLayoutInfoComputer.computeEntireDocumentMinimapLineCount({ + const { minimapLineCount } = EditorLayoutInfoComputer.computeContainedMinimapLineCount({ modelLineCount: modelLineCount, scrollBeyondLastLine: scrollBeyondLastLine, height: layoutInfo.height, diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 2ec1caef5072..07ccf3d6c98d 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -1686,6 +1686,7 @@ export interface EditorLayoutInfo { * The width of the minimap */ readonly minimapWidth: number; + readonly minimapHeightIsEditorHeight: boolean; readonly minimapIsSampling: boolean; readonly minimapScale: number; readonly minimapLineHeight: number; @@ -1758,7 +1759,7 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption= 2 ? Math.round(minimap.scale * 2) : minimap.scale); const minimapMaxColumn = minimap.maxColumn | 0; - const minimapEntireDocument = minimap.entireDocument; + const minimapMode = minimap.mode; const scrollbar = options.get(EditorOption.scrollbar); const verticalScrollbarWidth = scrollbar.verticalScrollbarSize | 0; @@ -1838,6 +1839,7 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption 1) { + minimapHeightIsEditorHeight = true; minimapIsSampling = true; minimapScale = 1; minimapLineHeight = 1; minimapCharWidth = minimapScale / pixelRatio; } else { - const configuredFontScale = minimapScale; - minimapLineHeight = Math.max(1, Math.floor(1 / desiredRatio)); - minimapScale = Math.min(configuredFontScale + 1, Math.max(1, Math.floor(minimapLineHeight / baseCharHeight))); - if (minimapScale > configuredFontScale) { - minimapWidthMultiplier = Math.min(2, minimapScale / configuredFontScale); + const effectiveMinimapHeight = Math.ceil((modelLineCount + extraLinesBeyondLastLine) * minimapLineHeight); + if (minimapMode === 'cover' || effectiveMinimapHeight > minimapCanvasInnerHeight) { + minimapHeightIsEditorHeight = true; + const configuredFontScale = minimapScale; + minimapLineHeight = Math.max(1, Math.floor(1 / desiredRatio)); + minimapScale = Math.min(configuredFontScale + 1, Math.max(1, Math.floor(minimapLineHeight / baseCharHeight))); + if (minimapScale > configuredFontScale) { + minimapWidthMultiplier = Math.min(2, minimapScale / configuredFontScale); + } + minimapCharWidth = minimapScale / pixelRatio / minimapWidthMultiplier; + minimapCanvasInnerHeight = Math.ceil((modelLineCount + extraLinesBeyondLastLine) * minimapLineHeight); } - minimapCharWidth = minimapScale / pixelRatio / minimapWidthMultiplier; - minimapCanvasInnerHeight = Math.ceil((modelLineCount + extraLinesBeyondLastLine) * minimapLineHeight); } } @@ -1944,6 +1951,7 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption>; @@ -2084,12 +2092,12 @@ class EditorMinimap extends BaseEditorOption(input.mode, this.defaultValue.mode, ['actual', 'cover', 'contain']), side: EditorStringEnumOption.stringSet<'right' | 'left'>(input.side, this.defaultValue.side, ['right', 'left']), showSlider: EditorStringEnumOption.stringSet<'always' | 'mouseover'>(input.showSlider, this.defaultValue.showSlider, ['always', 'mouseover']), renderCharacters: EditorBooleanOption.boolean(input.renderCharacters, this.defaultValue.renderCharacters), scale: EditorIntOption.clampedInt(input.scale, 1, 1, 3), maxColumn: EditorIntOption.clampedInt(input.maxColumn, this.defaultValue.maxColumn, 1, 10000), - entireDocument: EditorBooleanOption.boolean(input.entireDocument, this.defaultValue.entireDocument), }; } } diff --git a/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts b/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts index a69efcd6a929..f3c94bca5e32 100644 --- a/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts +++ b/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts @@ -33,7 +33,7 @@ interface IEditorLayoutProviderOpts { readonly minimapSide: 'left' | 'right'; readonly minimapRenderCharacters: boolean; readonly minimapMaxColumn: number; - minimapEntireDocument?: boolean; + minimapMode?: 'actual' | 'cover' | 'contain'; readonly pixelRatio: number; } @@ -47,12 +47,12 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { options._write(EditorOption.folding, false); const minimapOptions: EditorMinimapOptions = { enabled: input.minimap, + mode: input.minimapMode || 'actual', side: input.minimapSide, renderCharacters: input.minimapRenderCharacters, maxColumn: input.minimapMaxColumn, showSlider: 'mouseover', scale: 1, - entireDocument: input.minimapEntireDocument || false }; options._write(EditorOption.minimap, minimapOptions); const scrollbarOptions: InternalEditorScrollbarOptions = { @@ -129,6 +129,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.None, minimapLeft: 0, minimapWidth: 0, + minimapHeightIsEditorHeight: false, minimapIsSampling: false, minimapScale: 1, minimapLineHeight: 1, @@ -190,6 +191,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.None, minimapLeft: 0, minimapWidth: 0, + minimapHeightIsEditorHeight: false, minimapIsSampling: false, minimapScale: 1, minimapLineHeight: 1, @@ -251,6 +253,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.None, minimapLeft: 0, minimapWidth: 0, + minimapHeightIsEditorHeight: false, minimapIsSampling: false, minimapScale: 1, minimapLineHeight: 1, @@ -312,6 +315,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.None, minimapLeft: 0, minimapWidth: 0, + minimapHeightIsEditorHeight: false, minimapIsSampling: false, minimapScale: 1, minimapLineHeight: 1, @@ -373,6 +377,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.None, minimapLeft: 0, minimapWidth: 0, + minimapHeightIsEditorHeight: false, minimapIsSampling: false, minimapScale: 1, minimapLineHeight: 1, @@ -434,6 +439,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.None, minimapLeft: 0, minimapWidth: 0, + minimapHeightIsEditorHeight: false, minimapIsSampling: false, minimapScale: 1, minimapLineHeight: 1, @@ -495,6 +501,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.None, minimapLeft: 0, minimapWidth: 0, + minimapHeightIsEditorHeight: false, minimapIsSampling: false, minimapScale: 1, minimapLineHeight: 1, @@ -556,6 +563,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.None, minimapLeft: 0, minimapWidth: 0, + minimapHeightIsEditorHeight: false, minimapIsSampling: false, minimapScale: 1, minimapLineHeight: 1, @@ -617,6 +625,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.None, minimapLeft: 0, minimapWidth: 0, + minimapHeightIsEditorHeight: false, minimapIsSampling: false, minimapScale: 1, minimapLineHeight: 1, @@ -678,6 +687,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.None, minimapLeft: 0, minimapWidth: 0, + minimapHeightIsEditorHeight: false, minimapIsSampling: false, minimapScale: 1, minimapLineHeight: 1, @@ -739,6 +749,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.Text, minimapLeft: 903, minimapWidth: 97, + minimapHeightIsEditorHeight: false, minimapIsSampling: false, minimapScale: 1, minimapLineHeight: 2, @@ -800,6 +811,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.Text, minimapLeft: 903, minimapWidth: 97, + minimapHeightIsEditorHeight: false, minimapIsSampling: false, minimapScale: 2, minimapLineHeight: 4, @@ -861,6 +873,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.Text, minimapLeft: 945, minimapWidth: 55, + minimapHeightIsEditorHeight: false, minimapIsSampling: false, minimapScale: 2, minimapLineHeight: 4, @@ -922,6 +935,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.Text, minimapLeft: 0, minimapWidth: 55, + minimapHeightIsEditorHeight: false, minimapIsSampling: false, minimapScale: 2, minimapLineHeight: 4, @@ -943,7 +957,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { }); }); - test('EditorLayoutProvider 11 - render entire document in minimap without sampling', () => { + test('EditorLayoutProvider 11 - minimap mode cover without sampling', () => { doTest({ outerWidth: 1000, outerHeight: 800, @@ -964,7 +978,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { minimapSide: 'right', minimapRenderCharacters: true, minimapMaxColumn: 150, - minimapEntireDocument: true, + minimapMode: 'cover', pixelRatio: 2, }, { width: 1000, @@ -980,19 +994,20 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { decorationsWidth: 10, contentLeft: 10, - contentWidth: 818, + contentWidth: 893, renderMinimap: RenderMinimap.Text, - minimapLeft: 828, - minimapWidth: 172, + minimapLeft: 903, + minimapWidth: 97, + minimapHeightIsEditorHeight: true, minimapIsSampling: false, - minimapScale: 4, + minimapScale: 3, minimapLineHeight: 13, - minimapCanvasInnerWidth: 344, + minimapCanvasInnerWidth: 291, minimapCanvasInnerHeight: 1560, - minimapCanvasOuterWidth: 172, + minimapCanvasOuterWidth: 97, minimapCanvasOuterHeight: 800, - viewportColumn: 81, + viewportColumn: 89, verticalScrollbarWidth: 0, horizontalScrollbarHeight: 0, @@ -1006,7 +1021,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { }); }); - test('EditorLayoutProvider 12 - render entire document in minimap with sampling', () => { + test('EditorLayoutProvider 12 - minimap mode cover with sampling', () => { doTest({ outerWidth: 1000, outerHeight: 800, @@ -1027,7 +1042,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { minimapSide: 'right', minimapRenderCharacters: true, minimapMaxColumn: 150, - minimapEntireDocument: true, + minimapMode: 'cover', pixelRatio: 2, }, { width: 1000, @@ -1048,6 +1063,135 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.Text, minimapLeft: 945, minimapWidth: 55, + minimapHeightIsEditorHeight: true, + minimapIsSampling: true, + minimapScale: 1, + minimapLineHeight: 1, + minimapCanvasInnerWidth: 110, + minimapCanvasInnerHeight: 1600, + minimapCanvasOuterWidth: 55, + minimapCanvasOuterHeight: 800, + viewportColumn: 93, + + verticalScrollbarWidth: 0, + horizontalScrollbarHeight: 0, + + overviewRuler: { + top: 0, + width: 0, + height: 800, + right: 0 + } + }); + }); + + test('EditorLayoutProvider 13 - minimap mode contain without sampling', () => { + doTest({ + outerWidth: 1000, + outerHeight: 800, + showGlyphMargin: false, + lineHeight: 16, + showLineNumbers: false, + lineNumbersMinChars: 0, + lineNumbersDigitCount: 3, + maxLineNumber: 120, + lineDecorationsWidth: 10, + typicalHalfwidthCharacterWidth: 10, + maxDigitWidth: 10, + verticalScrollbarWidth: 0, + horizontalScrollbarHeight: 0, + scrollbarArrowSize: 0, + verticalScrollbarHasArrows: false, + minimap: true, + minimapSide: 'right', + minimapRenderCharacters: true, + minimapMaxColumn: 150, + minimapMode: 'contain', + pixelRatio: 2, + }, { + width: 1000, + height: 800, + + glyphMarginLeft: 0, + glyphMarginWidth: 0, + + lineNumbersLeft: 0, + lineNumbersWidth: 0, + + decorationsLeft: 0, + decorationsWidth: 10, + + contentLeft: 10, + contentWidth: 893, + + renderMinimap: RenderMinimap.Text, + minimapLeft: 903, + minimapWidth: 97, + minimapHeightIsEditorHeight: false, + minimapIsSampling: false, + minimapScale: 2, + minimapLineHeight: 4, + minimapCanvasInnerWidth: 194, + minimapCanvasInnerHeight: 1600, + minimapCanvasOuterWidth: 97, + minimapCanvasOuterHeight: 800, + viewportColumn: 89, + + verticalScrollbarWidth: 0, + horizontalScrollbarHeight: 0, + + overviewRuler: { + top: 0, + width: 0, + height: 800, + right: 0 + } + }); + }); + + test('EditorLayoutProvider 14 - minimap mode contain with sampling', () => { + doTest({ + outerWidth: 1000, + outerHeight: 800, + showGlyphMargin: false, + lineHeight: 16, + showLineNumbers: false, + lineNumbersMinChars: 0, + lineNumbersDigitCount: 4, + maxLineNumber: 2500, + lineDecorationsWidth: 10, + typicalHalfwidthCharacterWidth: 10, + maxDigitWidth: 10, + verticalScrollbarWidth: 0, + horizontalScrollbarHeight: 0, + scrollbarArrowSize: 0, + verticalScrollbarHasArrows: false, + minimap: true, + minimapSide: 'right', + minimapRenderCharacters: true, + minimapMaxColumn: 150, + minimapMode: 'contain', + pixelRatio: 2, + }, { + width: 1000, + height: 800, + + glyphMarginLeft: 0, + glyphMarginWidth: 0, + + lineNumbersLeft: 0, + lineNumbersWidth: 0, + + decorationsLeft: 0, + decorationsWidth: 10, + + contentLeft: 10, + contentWidth: 935, + + renderMinimap: RenderMinimap.Text, + minimapLeft: 945, + minimapWidth: 55, + minimapHeightIsEditorHeight: true, minimapIsSampling: true, minimapScale: 1, minimapLineHeight: 1, @@ -1109,6 +1253,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.Text, minimapLeft: 1096, minimapWidth: 91, + minimapHeightIsEditorHeight: false, minimapIsSampling: false, minimapScale: 2, minimapLineHeight: 4, diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 105a3a08e0ce..d5aae368b6af 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3330,6 +3330,7 @@ declare namespace monaco.editor { * The width of the minimap */ readonly minimapWidth: number; + readonly minimapHeightIsEditorHeight: boolean; readonly minimapIsSampling: boolean; readonly minimapScale: number; readonly minimapLineHeight: number; @@ -3386,6 +3387,11 @@ declare namespace monaco.editor { * Defaults to 'right'. */ side?: 'right' | 'left'; + /** + * Control the minimap rendering mode. + * Defaults to 'actual'. + */ + mode?: 'actual' | 'cover' | 'contain'; /** * Control the rendering of the minimap slider. * Defaults to 'mouseover'. @@ -3405,10 +3411,6 @@ declare namespace monaco.editor { * Relative size of the font in the minimap. Defaults to 1. */ scale?: number; - /** - * Minimap covers entire document. - */ - entireDocument?: boolean; } export type EditorMinimapOptions = Readonly>; From 2d125bc3cfeb9f4d0f95ace3860836d252a1a7b5 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 17 Feb 2020 12:19:37 +0100 Subject: [PATCH 094/109] remove git init action from top of scm --- extensions/git/package.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 2e2b8e57d9ff..dcfdcd2171c7 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -749,11 +749,6 @@ } ], "scm/title": [ - { - "command": "git.init", - "group": "navigation", - "when": "config.git.enabled && !scmProvider && gitOpenRepositoryCount == 0 && workspaceFolderCount != 0" - }, { "command": "git.commit", "group": "navigation", From f1c6f2c2002249b2432619bf6d668c7eb5456313 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 17 Feb 2020 12:33:09 +0100 Subject: [PATCH 095/109] text files - move save error handler into files model manager --- .../browser/editors/textFileSaveErrorHandler.ts | 2 +- .../services/textfile/browser/textFileService.ts | 13 ------------- .../textfile/common/textFileEditorModel.ts | 2 +- .../common/textFileEditorModelManager.ts | 16 +++++++++++++++- .../services/textfile/common/textfiles.ts | 8 ++------ .../electron-browser/nativeTextFileService.ts | 4 +--- .../test/browser/workbenchTestServices.ts | 2 -- .../electron-browser/workbenchTestServices.ts | 3 --- 8 files changed, 20 insertions(+), 30 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts b/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts index 5c214b0641df..af798c52dd85 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts @@ -64,7 +64,7 @@ export class TextFileSaveErrorHandler extends Disposable implements ISaveErrorHa this._register(textModelService.registerTextModelContentProvider(CONFLICT_RESOLUTION_SCHEME, provider)); // Set as save error handler to service for text files - this.textFileService.saveErrorHandler = this; + this.textFileService.files.saveErrorHandler = this; this.registerListeners(); } diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index c2d6d00c6ce3..94a613186842 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -33,8 +33,6 @@ import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel' import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { coalesce } from 'vs/base/common/arrays'; import { suggestFilename } from 'vs/base/common/mime'; -import { INotificationService } from 'vs/platform/notification/common/notification'; -import { toErrorMessage } from 'vs/base/common/errorMessage'; import { IRemotePathService } from 'vs/workbench/services/path/common/remotePathService'; import { isValidBasename } from 'vs/base/common/extpath'; @@ -59,16 +57,6 @@ export abstract class AbstractTextFileService extends Disposable implements ITex readonly untitled: IUntitledTextEditorModelManager = this.untitledTextEditorService; - saveErrorHandler = (() => { - const notificationService = this.notificationService; - - return { - onSaveError(error: Error, model: ITextFileEditorModel): void { - notificationService.error(nls.localize('genericSaveError', "Failed to save '{0}': {1}", model.name, toErrorMessage(error, false))); - } - }; - })(); - abstract get encoding(): IResourceEncodings; constructor( @@ -84,7 +72,6 @@ export abstract class AbstractTextFileService extends Disposable implements ITex @IFilesConfigurationService protected readonly filesConfigurationService: IFilesConfigurationService, @ITextModelService private readonly textModelService: ITextModelService, @ICodeEditorService private readonly codeEditorService: ICodeEditorService, - @INotificationService private readonly notificationService: INotificationService, @IRemotePathService private readonly remotePathService: IRemotePathService ) { super(); diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index b9ac20401005..5b622561b3ad 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -738,7 +738,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } // Show to user - this.textFileService.saveErrorHandler.onSaveError(error, this); + this.textFileService.files.saveErrorHandler.onSaveError(error, this); // Emit as event this._onDidSaveError.fire(); diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts index 12040a85830c..a3a6cdba9379 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { localize } from 'vs/nls'; +import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; @@ -18,6 +20,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { TextFileSaveParticipant } from 'vs/workbench/services/textfile/common/textFileSaveParticipant'; import { SaveReason } from 'vs/workbench/common/editor'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { INotificationService } from 'vs/platform/notification/common/notification'; export class TextFileEditorModelManager extends Disposable implements ITextFileEditorModelManager { @@ -42,6 +45,16 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE private readonly _onDidChangeOrphaned = this._register(new Emitter()); readonly onDidChangeOrphaned = this._onDidChangeOrphaned.event; + saveErrorHandler = (() => { + const notificationService = this.notificationService; + + return { + onSaveError(error: Error, model: ITextFileEditorModel): void { + notificationService.error(localize('genericSaveError', "Failed to save '{0}': {1}", model.name, toErrorMessage(error, false))); + } + }; + })(); + private readonly mapResourceToModel = new ResourceMap(); private readonly mapResourceToModelListeners = new ResourceMap(); private readonly mapResourceToDisposeListener = new ResourceMap(); @@ -52,7 +65,8 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE constructor( @ILifecycleService private readonly lifecycleService: ILifecycleService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IFileService private readonly fileService: IFileService + @IFileService private readonly fileService: IFileService, + @INotificationService private readonly notificationService: INotificationService ) { super(); diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index d89c66af2c9f..c9532133e000 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -52,12 +52,6 @@ export interface ITextFileService extends IDisposable { */ readonly encoding: IResourceEncodings; - /** - * The handler that should be called when saving fails. Can be overridden - * to handle save errors in a custom way. - */ - saveErrorHandler: ISaveErrorHandler; - /** * A resource is dirty if it has unsaved changes or is an untitled file not yet saved. * @@ -377,6 +371,8 @@ export interface ITextFileEditorModelManager { addSaveParticipant(participant: ITextFileSaveParticipant): IDisposable; runSaveParticipants(model: IResolvedTextFileEditorModel, context: { reason: SaveReason; }, token: CancellationToken): Promise + saveErrorHandler: ISaveErrorHandler; + disposeModel(model: ITextFileEditorModel): void; } diff --git a/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts index 34ffc230de40..def5df7ccbe8 100644 --- a/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts +++ b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts @@ -37,7 +37,6 @@ import { assign } from 'vs/base/common/objects'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { INotificationService } from 'vs/platform/notification/common/notification'; import { IRemotePathService } from 'vs/workbench/services/path/common/remotePathService'; export class NativeTextFileService extends AbstractTextFileService { @@ -56,10 +55,9 @@ export class NativeTextFileService extends AbstractTextFileService { @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService, @ITextModelService textModelService: ITextModelService, @ICodeEditorService codeEditorService: ICodeEditorService, - @INotificationService notificationService: INotificationService, @IRemotePathService remotePathService: IRemotePathService ) { - super(fileService, untitledTextEditorService, lifecycleService, instantiationService, modelService, environmentService, dialogService, fileDialogService, textResourceConfigurationService, filesConfigurationService, textModelService, codeEditorService, notificationService, remotePathService); + super(fileService, untitledTextEditorService, lifecycleService, instantiationService, modelService, environmentService, dialogService, fileDialogService, textResourceConfigurationService, filesConfigurationService, textModelService, codeEditorService, remotePathService); } private _encoding: EncodingOracle | undefined; diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 783d4d74a564..50efaef1f8ab 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -121,7 +121,6 @@ export class TestTextFileService extends BrowserTextFileService { @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService, @ITextModelService textModelService: ITextModelService, @ICodeEditorService codeEditorService: ICodeEditorService, - @INotificationService notificationService: INotificationService, @IRemotePathService remotePathService: IRemotePathService ) { super( @@ -137,7 +136,6 @@ export class TestTextFileService extends BrowserTextFileService { filesConfigurationService, textModelService, codeEditorService, - notificationService, remotePathService ); } diff --git a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts index 0857cc45c11b..db2309ddf7b8 100644 --- a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts @@ -22,7 +22,6 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { INotificationService } from 'vs/platform/notification/common/notification'; import { URI } from 'vs/base/common/uri'; import { IReadTextFileOptions, ITextFileStreamContent, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel'; @@ -62,7 +61,6 @@ export class TestTextFileService extends NativeTextFileService { @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService, @ITextModelService textModelService: ITextModelService, @ICodeEditorService codeEditorService: ICodeEditorService, - @INotificationService notificationService: INotificationService, @IRemotePathService remotePathService: IRemotePathService ) { super( @@ -79,7 +77,6 @@ export class TestTextFileService extends NativeTextFileService { filesConfigurationService, textModelService, codeEditorService, - notificationService, remotePathService ); } From 39becc83bf044d1a76a3720267796fcef38b10ce Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 17 Feb 2020 12:34:31 +0100 Subject: [PATCH 096/109] Don't render minimap slider if it cannot be dragged --- src/vs/editor/browser/viewParts/minimap/minimap.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index adc23678bfe0..9eeeb5c8257f 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -174,6 +174,7 @@ class MinimapLayout { */ public readonly scrollHeight: number; + public readonly sliderNeeded: boolean; private readonly _computedSliderRatio: number; /** @@ -197,6 +198,7 @@ class MinimapLayout { constructor( scrollTop: number, scrollHeight: number, + sliderNeeded: boolean, computedSliderRatio: number, sliderTop: number, sliderHeight: number, @@ -205,6 +207,7 @@ class MinimapLayout { ) { this.scrollTop = scrollTop; this.scrollHeight = scrollHeight; + this.sliderNeeded = sliderNeeded; this._computedSliderRatio = computedSliderRatio; this.sliderTop = sliderTop; this.sliderHeight = sliderHeight; @@ -252,7 +255,8 @@ class MinimapLayout { // in the same way `scrollTop` can move from 0 to `scrollHeight` - `viewportHeight`. const computedSliderRatio = (maxMinimapSliderTop) / (scrollHeight - viewportHeight); const sliderTop = (scrollTop * computedSliderRatio); - return new MinimapLayout(scrollTop, scrollHeight, computedSliderRatio, sliderTop, sliderHeight, 1, lineCount); + const sliderNeeded = (maxMinimapSliderTop > 0); + return new MinimapLayout(scrollTop, scrollHeight, sliderNeeded, computedSliderRatio, sliderTop, sliderHeight, 1, lineCount); } // The visible line count in a viewport can change due to a number of reasons: @@ -301,8 +305,8 @@ class MinimapLayout { // All lines fit in the minimap const startLineNumber = 1; const endLineNumber = lineCount; - - return new MinimapLayout(scrollTop, scrollHeight, computedSliderRatio, sliderTop, sliderHeight, startLineNumber, endLineNumber); + const sliderNeeded = (maxMinimapSliderTop > 0); + return new MinimapLayout(scrollTop, scrollHeight, sliderNeeded, computedSliderRatio, sliderTop, sliderHeight, startLineNumber, endLineNumber); } else { let startLineNumber = Math.max(1, Math.floor(viewportStartLineNumber - sliderTop * pixelRatio / minimapLineHeight)); @@ -321,7 +325,7 @@ class MinimapLayout { const endLineNumber = Math.min(lineCount, startLineNumber + minimapLinesFitting - 1); - return new MinimapLayout(scrollTop, scrollHeight, computedSliderRatio, sliderTop, sliderHeight, startLineNumber, endLineNumber); + return new MinimapLayout(scrollTop, scrollHeight, true, computedSliderRatio, sliderTop, sliderHeight, startLineNumber, endLineNumber); } } } @@ -1321,6 +1325,7 @@ class InnerMinimap extends Disposable { renderingCtx.scrollHeight, this._lastRenderData ? this._lastRenderData.renderedLayout : null ); + this._slider.setDisplay(layout.sliderNeeded ? 'block' : 'none'); this._slider.setTop(layout.sliderTop); this._slider.setHeight(layout.sliderHeight); From 355eea22ea9a98a5d0517ffeae5bd176e96bb185 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 17 Feb 2020 12:33:03 +0100 Subject: [PATCH 097/109] linked text: class --- src/vs/base/browser/linkedText.ts | 15 +++++++++++++-- .../browser/parts/views/viewPaneContainer.ts | 4 ++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/vs/base/browser/linkedText.ts b/src/vs/base/browser/linkedText.ts index 3d69948aa427..ee268d9fdcd2 100644 --- a/src/vs/base/browser/linkedText.ts +++ b/src/vs/base/browser/linkedText.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { memoize } from 'vs/base/common/decorators'; + export interface ILink { readonly label: string; readonly href: string; @@ -10,7 +12,16 @@ export interface ILink { } export type LinkedTextNode = string | ILink; -export type LinkedText = LinkedTextNode[]; + +export class LinkedText { + + constructor(readonly nodes: LinkedTextNode[]) { } + + @memoize + toString(): string { + return this.nodes.map(node => typeof node === 'string' ? node : node.label).join(''); + } +} const LINK_REGEX = /\[([^\]]+)\]\(((?:https?:\/\/|command:)[^\)\s]+)(?: "([^"]+)")?\)/gi; @@ -40,5 +51,5 @@ export function parseLinkedText(text: string): LinkedText { result.push(text.substring(index)); } - return result; + return new LinkedText(result); } diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index df18c1c388bc..37221c09c15f 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -402,10 +402,10 @@ export abstract class ViewPane extends Pane implements IView { const p = append(this.viewWelcomeContainer, $('p')); const linkedText = parseLinkedText(line); - for (const node of linkedText) { + for (const node of linkedText.nodes) { if (typeof node === 'string') { append(p, document.createTextNode(node)); - } else if (linkedText.length === 1) { + } else if (linkedText.nodes.length === 1) { const button = new Button(p, { title: node.title }); button.label = node.label; button.onDidClick(_ => this.openerService.open(node.href), null, disposables); From 2807512bce099a4baede0441a652bd2a2309357e Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 17 Feb 2020 12:50:18 +0100 Subject: [PATCH 098/109] Do not stretch minimap lines so much in very small cases --- src/vs/editor/common/config/editorOptions.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 07ccf3d6c98d..81949c640473 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -1765,11 +1765,12 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption minimapCanvasInnerHeight) { minimapHeightIsEditorHeight = true; const configuredFontScale = minimapScale; - minimapLineHeight = Math.max(1, Math.floor(1 / desiredRatio)); + minimapLineHeight = Math.min(lineHeight * pixelRatio, Math.max(1, Math.floor(1 / desiredRatio))); minimapScale = Math.min(configuredFontScale + 1, Math.max(1, Math.floor(minimapLineHeight / baseCharHeight))); if (minimapScale > configuredFontScale) { minimapWidthMultiplier = Math.min(2, minimapScale / configuredFontScale); } minimapCharWidth = minimapScale / pixelRatio / minimapWidthMultiplier; - minimapCanvasInnerHeight = Math.ceil((modelLineCount + extraLinesBeyondLastLine) * minimapLineHeight); + minimapCanvasInnerHeight = Math.ceil((Math.max(typicalViewportLineCount, modelLineCount + extraLinesBeyondLastLine)) * minimapLineHeight); } } } From c150c04fb59799a32ae163010097f56a684a6ce6 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 17 Feb 2020 12:55:00 +0100 Subject: [PATCH 099/109] Remove assumption about the height ratio being the pixel ratio --- src/vs/editor/browser/viewParts/minimap/minimap.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index 9eeeb5c8257f..56331186a6fd 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -1102,7 +1102,7 @@ class InnerMinimap extends Disposable { return; } const minimapLineHeight = this._model.options.minimapLineHeight; - const internalOffsetY = this._model.options.pixelRatio * e.browserEvent.offsetY; + const internalOffsetY = (this._model.options.canvasInnerHeight / this._model.options.canvasOuterHeight) * e.browserEvent.offsetY; const lineIndex = Math.floor(internalOffsetY / minimapLineHeight); let lineNumber = lineIndex + this._lastRenderData.renderedLayout.startLineNumber; From 22e83f212c63021ca9d754480cb11369d9657c4d Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 17 Feb 2020 14:00:01 +0100 Subject: [PATCH 100/109] Add click & drag on the minimap when it has the full editor height --- .../browser/viewParts/minimap/minimap.ts | 67 ++++++++++++------- 1 file changed, 41 insertions(+), 26 deletions(-) diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index 56331186a6fd..810b43e48470 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -1101,6 +1101,15 @@ class InnerMinimap extends Disposable { if (!this._lastRenderData) { return; } + if (this._model.options.minimapHeightIsEditorHeight) { + if (e.leftButton && this._lastRenderData) { + // pretend the click occured in the center of the slider + const position = dom.getDomNodePagePosition(this._slider.domNode); + const initialPosY = position.top + position.height / 2; + this._startSliderDragging(e.buttons, e.posx, initialPosY, e.posy, this._lastRenderData.renderedLayout); + } + return; + } const minimapLineHeight = this._model.options.minimapLineHeight; const internalOffsetY = (this._model.options.canvasInnerHeight / this._model.options.canvasOuterHeight) * e.browserEvent.offsetY; const lineIndex = Math.floor(internalOffsetY / minimapLineHeight); @@ -1117,32 +1126,7 @@ class InnerMinimap extends Disposable { e.preventDefault(); e.stopPropagation(); if (e.leftButton && this._lastRenderData) { - - const initialMousePosition = e.posy; - const initialMouseOrthogonalPosition = e.posx; - const initialSliderState = this._lastRenderData.renderedLayout; - this._slider.toggleClassName('active', true); - - this._sliderMouseMoveMonitor.startMonitoring( - e.target, - e.buttons, - standardMouseMoveMerger, - (mouseMoveData: IStandardMouseMoveEventData) => { - const mouseOrthogonalDelta = Math.abs(mouseMoveData.posx - initialMouseOrthogonalPosition); - - if (platform.isWindows && mouseOrthogonalDelta > MOUSE_DRAG_RESET_DISTANCE) { - // The mouse has wondered away from the scrollbar => reset dragging - this._model.setScrollTop(initialSliderState.scrollTop); - return; - } - - const mouseDelta = mouseMoveData.posy - initialMousePosition; - this._model.setScrollTop(initialSliderState.getDesiredScrollTopFromDelta(mouseDelta)); - }, - () => { - this._slider.toggleClassName('active', false); - } - ); + this._startSliderDragging(e.buttons, e.posx, e.posy, e.posy, this._lastRenderData.renderedLayout); } }); @@ -1173,6 +1157,37 @@ class InnerMinimap extends Disposable { }); } + private _startSliderDragging(initialButtons: number, initialPosX: number, initialPosY: number, posy: number, initialSliderState: MinimapLayout): void { + this._slider.toggleClassName('active', true); + + const handleMouseMove = (posy: number, posx: number) => { + const mouseOrthogonalDelta = Math.abs(posx - initialPosX); + + if (platform.isWindows && mouseOrthogonalDelta > MOUSE_DRAG_RESET_DISTANCE) { + // The mouse has wondered away from the scrollbar => reset dragging + this._model.setScrollTop(initialSliderState.scrollTop); + return; + } + + const mouseDelta = posy - initialPosY; + this._model.setScrollTop(initialSliderState.getDesiredScrollTopFromDelta(mouseDelta)); + }; + + if (posy !== initialPosY) { + handleMouseMove(posy, initialPosX); + } + + this._sliderMouseMoveMonitor.startMonitoring( + this._slider.domNode, + initialButtons, + standardMouseMoveMerger, + (mouseMoveData: IStandardMouseMoveEventData) => handleMouseMove(mouseMoveData.posy, mouseMoveData.posx), + () => { + this._slider.toggleClassName('active', false); + } + ); + } + private scrollDueToTouchEvent(touch: GestureEvent) { const startY = this._domNode.domNode.getBoundingClientRect().top; const scrollTop = this._lastRenderData!.renderedLayout.getDesiredScrollTopFromTouchLocation(touch.pageY - startY); From 9fde1ae0e081f80d644d39cefc989593789bfb7c Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 17 Feb 2020 14:48:04 +0100 Subject: [PATCH 101/109] Improve warning check --- .../editor/browser/viewParts/minimap/minimapCharRenderer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/browser/viewParts/minimap/minimapCharRenderer.ts b/src/vs/editor/browser/viewParts/minimap/minimapCharRenderer.ts index 483abbaac3b2..a789082e76b8 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimapCharRenderer.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimapCharRenderer.ts @@ -39,7 +39,8 @@ export class MinimapCharRenderer { ): void { const charWidth = Constants.BASE_CHAR_WIDTH * this.scale; const charHeight = Constants.BASE_CHAR_HEIGHT * this.scale; - if (dx + charWidth > target.width || dy + charHeight > target.height) { + const renderHeight = (force1pxHeight ? 1 : charHeight); + if (dx + charWidth > target.width || dy + renderHeight > target.height) { console.warn('bad render request outside image data'); return; } @@ -61,7 +62,6 @@ export class MinimapCharRenderer { let sourceOffset = charIndex * charWidth * charHeight; let row = dy * destWidth + dx * Constants.RGBA_CHANNELS_CNT; - const renderHeight = (force1pxHeight ? 1 : charHeight); for (let y = 0; y < renderHeight; y++) { let column = row; for (let x = 0; x < charWidth; x++) { From 9931bab3e98e37f1c4bacf5608d8db4e50cf59d7 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 17 Feb 2020 14:50:42 +0100 Subject: [PATCH 102/109] Improve rendering for minimap blocks --- src/vs/editor/browser/viewParts/minimap/minimap.ts | 5 +++-- .../browser/viewParts/minimap/minimapCharRenderer.ts | 8 +++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index 810b43e48470..495ae096449a 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -1666,6 +1666,7 @@ class InnerMinimap extends Disposable { const content = lineData.content; const tokens = lineData.tokens; const maxDx = target.width - charWidth; + const force1pxHeight = (minimapLineHeight === 1); let dx = MINIMAP_GUTTER_WIDTH; let charIndex = 0; @@ -1697,9 +1698,9 @@ class InnerMinimap extends Disposable { for (let i = 0; i < count; i++) { if (renderMinimap === RenderMinimap.Blocks) { - minimapCharRenderer.blockRenderChar(target, dx, dy + innerLinePadding, tokenColor, backgroundColor, useLighterFont); + minimapCharRenderer.blockRenderChar(target, dx, dy + innerLinePadding, tokenColor, backgroundColor, useLighterFont, force1pxHeight); } else { // RenderMinimap.Text - minimapCharRenderer.renderChar(target, dx, dy + innerLinePadding, charCode, tokenColor, backgroundColor, fontScale, useLighterFont, minimapLineHeight === 1); + minimapCharRenderer.renderChar(target, dx, dy + innerLinePadding, charCode, tokenColor, backgroundColor, fontScale, useLighterFont, force1pxHeight); } dx += charWidth; diff --git a/src/vs/editor/browser/viewParts/minimap/minimapCharRenderer.ts b/src/vs/editor/browser/viewParts/minimap/minimapCharRenderer.ts index a789082e76b8..c30b720c207a 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimapCharRenderer.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimapCharRenderer.ts @@ -82,11 +82,13 @@ export class MinimapCharRenderer { dy: number, color: RGBA8, backgroundColor: RGBA8, - useLighterFont: boolean + useLighterFont: boolean, + force1pxHeight: boolean ): void { const charWidth = Constants.BASE_CHAR_WIDTH * this.scale; const charHeight = Constants.BASE_CHAR_HEIGHT * this.scale; - if (dx + charWidth > target.width || dy + charHeight > target.height) { + const renderHeight = (force1pxHeight ? 1 : charHeight); + if (dx + charWidth > target.width || dy + renderHeight > target.height) { console.warn('bad render request outside image data'); return; } @@ -110,7 +112,7 @@ export class MinimapCharRenderer { const dest = target.data; let row = dy * destWidth + dx * Constants.RGBA_CHANNELS_CNT; - for (let y = 0; y < charHeight; y++) { + for (let y = 0; y < renderHeight; y++) { let column = row; for (let x = 0; x < charWidth; x++) { dest[column++] = colorR; From 41781a6d2446820061ed1599e751843bc60e0032 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 17 Feb 2020 15:07:41 +0100 Subject: [PATCH 103/109] move LinkedText to common --- src/vs/base/{browser => common}/linkedText.ts | 0 src/vs/base/test/{browser => common}/linkedText.test.ts | 2 +- src/vs/workbench/browser/parts/views/viewPaneContainer.ts | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename src/vs/base/{browser => common}/linkedText.ts (100%) rename src/vs/base/test/{browser => common}/linkedText.test.ts (97%) diff --git a/src/vs/base/browser/linkedText.ts b/src/vs/base/common/linkedText.ts similarity index 100% rename from src/vs/base/browser/linkedText.ts rename to src/vs/base/common/linkedText.ts diff --git a/src/vs/base/test/browser/linkedText.test.ts b/src/vs/base/test/common/linkedText.test.ts similarity index 97% rename from src/vs/base/test/browser/linkedText.test.ts rename to src/vs/base/test/common/linkedText.test.ts index 2c765555ffe7..eb8f85f2803a 100644 --- a/src/vs/base/test/browser/linkedText.test.ts +++ b/src/vs/base/test/common/linkedText.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { parseLinkedText } from 'vs/base/browser/linkedText'; +import { parseLinkedText } from 'vs/base/common/linkedText'; suite('LinkedText', () => { test('parses correctly', () => { diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 37221c09c15f..074150b643d5 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -38,7 +38,7 @@ import { Component } from 'vs/workbench/common/component'; import { MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; import { ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { ViewMenuActions } from 'vs/workbench/browser/parts/views/viewMenuActions'; -import { parseLinkedText } from 'vs/base/browser/linkedText'; +import { parseLinkedText } from 'vs/base/common/linkedText'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { Button } from 'vs/base/browser/ui/button/button'; import { Link } from 'vs/platform/opener/browser/link'; From 659816653e6bd1b102aaa0f46c63a9d024227492 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 17 Feb 2020 15:25:48 +0100 Subject: [PATCH 104/109] Emit a tokens change event only if there are changed tokens --- src/vs/editor/common/model/textModel.ts | 32 ++++++++++++++++++----- src/vs/editor/common/model/tokensStore.ts | 27 ++++++++++++++++++- 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 2c1fcc8f2481..bfd6c4b9e6ac 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -1713,7 +1713,7 @@ export class TextModel extends Disposable implements model.ITextModel { throw new Error('Illegal value for lineNumber'); } - this._tokens.setTokens(this._languageIdentifier.id, lineNumber - 1, this._buffer.getLineLength(lineNumber), tokens); + this._tokens.setTokens(this._languageIdentifier.id, lineNumber - 1, this._buffer.getLineLength(lineNumber), tokens, false); } public setTokens(tokens: MultilineTokens[]): void { @@ -1725,16 +1725,34 @@ export class TextModel extends Disposable implements model.ITextModel { for (let i = 0, len = tokens.length; i < len; i++) { const element = tokens[i]; - ranges.push({ fromLineNumber: element.startLineNumber, toLineNumber: element.startLineNumber + element.tokens.length - 1 }); + let minChangedLineNumber = 0; + let maxChangedLineNumber = 0; + let hasChange = false; for (let j = 0, lenJ = element.tokens.length; j < lenJ; j++) { - this.setLineTokens(element.startLineNumber + j, element.tokens[j]); + const lineNumber = element.startLineNumber + j; + if (hasChange) { + this._tokens.setTokens(this._languageIdentifier.id, lineNumber - 1, this._buffer.getLineLength(lineNumber), element.tokens[j], false); + maxChangedLineNumber = lineNumber; + } else { + const lineHasChange = this._tokens.setTokens(this._languageIdentifier.id, lineNumber - 1, this._buffer.getLineLength(lineNumber), element.tokens[j], true); + if (lineHasChange) { + hasChange = true; + minChangedLineNumber = lineNumber; + maxChangedLineNumber = lineNumber; + } + } + } + if (hasChange) { + ranges.push({ fromLineNumber: minChangedLineNumber, toLineNumber: maxChangedLineNumber }); } } - this._emitModelTokensChangedEvent({ - tokenizationSupportChanged: false, - ranges: ranges - }); + if (ranges.length > 0) { + this._emitModelTokensChangedEvent({ + tokenizationSupportChanged: false, + ranges: ranges + }); + } } public setSemanticTokens(tokens: MultilineTokens2[] | null): void { diff --git a/src/vs/editor/common/model/tokensStore.ts b/src/vs/editor/common/model/tokensStore.ts index e396aa054a2c..c662f820c9ca 100644 --- a/src/vs/editor/common/model/tokensStore.ts +++ b/src/vs/editor/common/model/tokensStore.ts @@ -964,10 +964,35 @@ export class TokensStore { this._len += insertCount; } - public setTokens(topLevelLanguageId: LanguageId, lineIndex: number, lineTextLength: number, _tokens: Uint32Array | ArrayBuffer | null): void { + public setTokens(topLevelLanguageId: LanguageId, lineIndex: number, lineTextLength: number, _tokens: Uint32Array | ArrayBuffer | null, checkEquality: boolean): boolean { const tokens = TokensStore._massageTokens(topLevelLanguageId, lineTextLength, _tokens); this._ensureLine(lineIndex); + const oldTokens = this._lineTokens[lineIndex]; this._lineTokens[lineIndex] = tokens; + + if (checkEquality) { + return !TokensStore._equals(oldTokens, tokens); + } + return false; + } + + private static _equals(_a: Uint32Array | ArrayBuffer | null, _b: Uint32Array | ArrayBuffer | null) { + if (!_a || !_b) { + return !_a && !_b; + } + + const a = toUint32Array(_a); + const b = toUint32Array(_b); + + if (a.length !== b.length) { + return false; + } + for (let i = 0, len = a.length; i < len; i++) { + if (a[i] !== b[i]) { + return false; + } + } + return true; } //#region Editing From fd7ea40bfedb594f51439c9b97d2838e6a1fd95c Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 17 Feb 2020 15:34:12 +0100 Subject: [PATCH 105/109] fix tests --- src/vs/base/test/common/linkedText.test.ts | 24 +++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/vs/base/test/common/linkedText.test.ts b/src/vs/base/test/common/linkedText.test.ts index eb8f85f2803a..8e1cb887485d 100644 --- a/src/vs/base/test/common/linkedText.test.ts +++ b/src/vs/base/test/common/linkedText.test.ts @@ -8,46 +8,46 @@ import { parseLinkedText } from 'vs/base/common/linkedText'; suite('LinkedText', () => { test('parses correctly', () => { - assert.deepEqual(parseLinkedText(''), []); - assert.deepEqual(parseLinkedText('hello'), ['hello']); - assert.deepEqual(parseLinkedText('hello there'), ['hello there']); - assert.deepEqual(parseLinkedText('Some message with [link text](http://link.href).'), [ + assert.deepEqual(parseLinkedText('').nodes, []); + assert.deepEqual(parseLinkedText('hello').nodes, ['hello']); + assert.deepEqual(parseLinkedText('hello there').nodes, ['hello there']); + assert.deepEqual(parseLinkedText('Some message with [link text](http://link.href).').nodes, [ 'Some message with ', { label: 'link text', href: 'http://link.href' }, '.' ]); - assert.deepEqual(parseLinkedText('Some message with [link text](http://link.href "and a title").'), [ + assert.deepEqual(parseLinkedText('Some message with [link text](http://link.href "and a title").').nodes, [ 'Some message with ', { label: 'link text', href: 'http://link.href', title: 'and a title' }, '.' ]); - assert.deepEqual(parseLinkedText('Some message with [link text](random stuff).'), [ + assert.deepEqual(parseLinkedText('Some message with [link text](random stuff).').nodes, [ 'Some message with [link text](random stuff).' ]); - assert.deepEqual(parseLinkedText('Some message with [https link](https://link.href).'), [ + assert.deepEqual(parseLinkedText('Some message with [https link](https://link.href).').nodes, [ 'Some message with ', { label: 'https link', href: 'https://link.href' }, '.' ]); - assert.deepEqual(parseLinkedText('Some message with [https link](https:).'), [ + assert.deepEqual(parseLinkedText('Some message with [https link](https:).').nodes, [ 'Some message with [https link](https:).' ]); - assert.deepEqual(parseLinkedText('Some message with [a command](command:foobar).'), [ + assert.deepEqual(parseLinkedText('Some message with [a command](command:foobar).').nodes, [ 'Some message with ', { label: 'a command', href: 'command:foobar' }, '.' ]); - assert.deepEqual(parseLinkedText('Some message with [a command](command:).'), [ + assert.deepEqual(parseLinkedText('Some message with [a command](command:).').nodes, [ 'Some message with [a command](command:).' ]); - assert.deepEqual(parseLinkedText('link [one](command:foo "nice") and link [two](http://foo)...'), [ + assert.deepEqual(parseLinkedText('link [one](command:foo "nice") and link [two](http://foo)...').nodes, [ 'link ', { label: 'one', href: 'command:foo', title: 'nice' }, ' and link ', { label: 'two', href: 'http://foo' }, '...' ]); - assert.deepEqual(parseLinkedText('link\n[one](command:foo "nice")\nand link [two](http://foo)...'), [ + assert.deepEqual(parseLinkedText('link\n[one](command:foo "nice")\nand link [two](http://foo)...').nodes, [ 'link\n', { label: 'one', href: 'command:foo', title: 'nice' }, '\nand link ', From 95cc4e3d835ab7bb79b199169c506669ed5d3026 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 17 Feb 2020 15:51:51 +0100 Subject: [PATCH 106/109] Have the IModelDecorationsChangedEvent contain affectsMinimap and affectsOverviewRuler to avoid repainting if not necessary --- .../browser/viewParts/minimap/minimap.ts | 5 ++- .../overviewRuler/decorationsOverviewRuler.ts | 5 ++- src/vs/editor/common/model/textModel.ts | 36 +++++++++++++++---- src/vs/editor/common/model/textModelEvents.ts | 2 ++ src/vs/editor/common/view/viewEvents.ts | 14 ++++++-- .../editor/common/viewModel/viewModelImpl.ts | 12 +++---- src/vs/monaco.d.ts | 2 ++ 7 files changed, 59 insertions(+), 17 deletions(-) diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index 495ae096449a..e3cbf8ce4d35 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -781,7 +781,10 @@ export class Minimap extends ViewPart implements IMinimapModel { return this._actual.onSelectionChanged(); } public onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { - return this._actual.onDecorationsChanged(); + if (e.affectsMinimap) { + return this._actual.onDecorationsChanged(); + } + return false; } public onFlushed(e: viewEvents.ViewFlushedEvent): boolean { return this._actual.onFlushed(); diff --git a/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts b/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts index 0cbac3c5035c..bd1aa10f8a8f 100644 --- a/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts +++ b/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts @@ -276,7 +276,10 @@ export class DecorationsOverviewRuler extends ViewPart { return true; } public onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { - return true; + if (e.affectsOverviewRuler) { + return true; + } + return false; } public onFlushed(e: viewEvents.ViewFlushedEvent): boolean { return true; diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index bfd6c4b9e6ac..8aa9b5a53d48 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -1423,19 +1423,15 @@ export class TextModel extends Disposable implements model.ITextModel { private _changeDecorations(ownerId: number, callback: (changeAccessor: model.IModelDecorationsChangeAccessor) => T): T | null { let changeAccessor: model.IModelDecorationsChangeAccessor = { addDecoration: (range: IRange, options: model.IModelDecorationOptions): string => { - this._onDidChangeDecorations.fire(); return this._deltaDecorationsImpl(ownerId, [], [{ range: range, options: options }])[0]; }, changeDecoration: (id: string, newRange: IRange): void => { - this._onDidChangeDecorations.fire(); this._changeDecorationImpl(id, newRange); }, changeDecorationOptions: (id: string, options: model.IModelDecorationOptions) => { - this._onDidChangeDecorations.fire(); this._changeDecorationOptionsImpl(id, _normalizeOptions(options)); }, removeDecoration: (id: string): void => { - this._onDidChangeDecorations.fire(); this._deltaDecorationsImpl(ownerId, [id], []); }, deltaDecorations: (oldDecorations: string[], newDecorations: model.IModelDeltaDecoration[]): string[] => { @@ -1443,7 +1439,6 @@ export class TextModel extends Disposable implements model.ITextModel { // nothing to do return []; } - this._onDidChangeDecorations.fire(); return this._deltaDecorationsImpl(ownerId, oldDecorations, newDecorations); } }; @@ -1474,7 +1469,6 @@ export class TextModel extends Disposable implements model.ITextModel { try { this._onDidChangeDecorations.beginDeferredEmit(); - this._onDidChangeDecorations.fire(); return this._deltaDecorationsImpl(ownerId, oldDecorations, newDecorations); } finally { this._onDidChangeDecorations.endDeferredEmit(); @@ -1622,6 +1616,7 @@ export class TextModel extends Disposable implements model.ITextModel { this._decorationsTree.delete(node); node.reset(this.getVersionId(), startOffset, endOffset, range); this._decorationsTree.insert(node); + this._onDidChangeDecorations.checkAffectedAndFire(node.options); } private _changeDecorationOptionsImpl(decorationId: string, options: ModelDecorationOptions): void { @@ -1633,6 +1628,9 @@ export class TextModel extends Disposable implements model.ITextModel { const nodeWasInOverviewRuler = (node.options.overviewRuler && node.options.overviewRuler.color ? true : false); const nodeIsInOverviewRuler = (options.overviewRuler && options.overviewRuler.color ? true : false); + this._onDidChangeDecorations.checkAffectedAndFire(node.options); + this._onDidChangeDecorations.checkAffectedAndFire(options); + if (nodeWasInOverviewRuler !== nodeIsInOverviewRuler) { // Delete + Insert due to an overview ruler status change this._decorationsTree.delete(node); @@ -1666,6 +1664,7 @@ export class TextModel extends Disposable implements model.ITextModel { // (2) remove the node from the tree (if it exists) if (node) { this._decorationsTree.delete(node); + this._onDidChangeDecorations.checkAffectedAndFire(node.options); } } @@ -1688,6 +1687,7 @@ export class TextModel extends Disposable implements model.ITextModel { node.ownerId = ownerId; node.reset(versionId, startOffset, endOffset, range); node.setOptions(options); + this._onDidChangeDecorations.checkAffectedAndFire(options); this._decorationsTree.insert(node); @@ -3101,11 +3101,15 @@ export class DidChangeDecorationsEmitter extends Disposable { private _deferredCnt: number; private _shouldFire: boolean; + private _affectsMinimap: boolean; + private _affectsOverviewRuler: boolean; constructor() { super(); this._deferredCnt = 0; this._shouldFire = false; + this._affectsMinimap = false; + this._affectsOverviewRuler = false; } public beginDeferredEmit(): void { @@ -3116,13 +3120,31 @@ export class DidChangeDecorationsEmitter extends Disposable { this._deferredCnt--; if (this._deferredCnt === 0) { if (this._shouldFire) { + const event: IModelDecorationsChangedEvent = { + affectsMinimap: this._affectsMinimap, + affectsOverviewRuler: this._affectsOverviewRuler, + }; this._shouldFire = false; - this._actual.fire({}); + this._affectsMinimap = false; + this._affectsOverviewRuler = false; + this._actual.fire(event); } } } + public checkAffectedAndFire(options: ModelDecorationOptions): void { + if (!this._affectsMinimap) { + this._affectsMinimap = options.minimap && options.minimap.position ? true : false; + } + if (!this._affectsOverviewRuler) { + this._affectsOverviewRuler = options.overviewRuler && options.overviewRuler.color ? true : false; + } + this._shouldFire = true; + } + public fire(): void { + this._affectsMinimap = true; + this._affectsOverviewRuler = true; this._shouldFire = true; } } diff --git a/src/vs/editor/common/model/textModelEvents.ts b/src/vs/editor/common/model/textModelEvents.ts index 11fc0e4165f6..a09a1abf0fe4 100644 --- a/src/vs/editor/common/model/textModelEvents.ts +++ b/src/vs/editor/common/model/textModelEvents.ts @@ -76,6 +76,8 @@ export interface IModelContentChangedEvent { * An event describing that model decorations have changed. */ export interface IModelDecorationsChangedEvent { + readonly affectsMinimap: boolean; + readonly affectsOverviewRuler: boolean; } /** diff --git a/src/vs/editor/common/view/viewEvents.ts b/src/vs/editor/common/view/viewEvents.ts index 1d9290b5e764..0f2720d8514b 100644 --- a/src/vs/editor/common/view/viewEvents.ts +++ b/src/vs/editor/common/view/viewEvents.ts @@ -10,6 +10,7 @@ import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { ScrollType, IContentSizeChangedEvent } from 'vs/editor/common/editorCommon'; +import { IModelDecorationsChangedEvent } from 'vs/editor/common/model/textModelEvents'; export const enum ViewEventType { ViewConfigurationChanged = 1, @@ -82,8 +83,17 @@ export class ViewDecorationsChangedEvent { public readonly type = ViewEventType.ViewDecorationsChanged; - constructor() { - // Nothing to do + readonly affectsMinimap: boolean; + readonly affectsOverviewRuler: boolean; + + constructor(source: IModelDecorationsChangedEvent | null) { + if (source) { + this.affectsMinimap = source.affectsMinimap; + this.affectsOverviewRuler = source.affectsOverviewRuler; + } else { + this.affectsMinimap = true; + this.affectsOverviewRuler = true; + } } } diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index ed3b33825357..171a4839949e 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -172,7 +172,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel if (this.lines.setWrappingSettings(fontInfo, wrappingStrategy, wrappingInfo.wrappingColumn, wrappingIndent)) { eventsCollector.emit(new viewEvents.ViewFlushedEvent()); eventsCollector.emit(new viewEvents.ViewLineMappingChangedEvent()); - eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent()); + eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent(null)); this.decorations.onLineMappingChanged(); this.viewLayout.onFlushed(this.getLineCount()); @@ -185,7 +185,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel if (e.hasChanged(EditorOption.readOnly)) { // Must read again all decorations due to readOnly filtering this.decorations.reset(); - eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent()); + eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent(null)); } eventsCollector.emit(new viewEvents.ViewConfigurationChangedEvent(e)); @@ -291,7 +291,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel if (!hadOtherModelChange && hadModelLineChangeThatChangedLineMapping) { eventsCollector.emit(new viewEvents.ViewLineMappingChangedEvent()); - eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent()); + eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent(null)); this.decorations.onLineMappingChanged(); } } finally { @@ -354,7 +354,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel const eventsCollector = this._beginEmit(); eventsCollector.emit(new viewEvents.ViewFlushedEvent()); eventsCollector.emit(new viewEvents.ViewLineMappingChangedEvent()); - eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent()); + eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent(null)); } finally { this._endEmit(); } @@ -365,7 +365,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel this.decorations.onModelDecorationsChanged(); try { const eventsCollector = this._beginEmit(); - eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent()); + eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent(e)); } finally { this._endEmit(); } @@ -379,7 +379,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel if (lineMappingChanged) { eventsCollector.emit(new viewEvents.ViewFlushedEvent()); eventsCollector.emit(new viewEvents.ViewLineMappingChangedEvent()); - eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent()); + eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent(null)); this.decorations.onLineMappingChanged(); this.viewLayout.onFlushed(this.getLineCount()); this.viewLayout.onHeightMaybeChanged(); diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index d5aae368b6af..73a615757dc4 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -2390,6 +2390,8 @@ declare namespace monaco.editor { * An event describing that model decorations have changed. */ export interface IModelDecorationsChangedEvent { + readonly affectsMinimap: boolean; + readonly affectsOverviewRuler: boolean; } export interface IModelOptionsChangedEvent { From b29d61c3059226740d27d89fdc66eccc0b196c56 Mon Sep 17 00:00:00 2001 From: isidor Date: Mon, 17 Feb 2020 15:53:28 +0100 Subject: [PATCH 107/109] fixes #90814 --- src/vs/editor/contrib/find/findWidget.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/find/findWidget.ts b/src/vs/editor/contrib/find/findWidget.ts index 96c7b3a0d17b..440c78fef727 100644 --- a/src/vs/editor/contrib/find/findWidget.ts +++ b/src/vs/editor/contrib/find/findWidget.ts @@ -419,8 +419,9 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas } if (currentMatch) { const ariaLabel = nls.localize('ariaSearchNoResultWithLineNum', "{0} found for '{1}', at {2}", label, searchString, currentMatch.startLineNumber + ':' + currentMatch.startColumn); - const lineContent = this._codeEditor.getModel()?.getLineContent(currentMatch.startLineNumber); - if (lineContent) { + const model = this._codeEditor.getModel(); + if (model && (currentMatch.startLineNumber <= model.getLineCount()) && (currentMatch.startLineNumber >= 1)) { + const lineContent = model.getLineContent(currentMatch.startLineNumber); return `${lineContent}, ${ariaLabel}`; } From 2e3bbc2da9a88fd3cbace1e8a3a0d1249a39b5ab Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 17 Feb 2020 16:30:55 +0100 Subject: [PATCH 108/109] always launch code with `--no-sandbox` --- scripts/code.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/code.sh b/scripts/code.sh index 4ba1a00b9f7c..0afe96bfcb6a 100755 --- a/scripts/code.sh +++ b/scripts/code.sh @@ -50,7 +50,7 @@ function code() { export VSCODE_LOGS= # Launch Code - exec "$CODE" . "$@" + exec "$CODE" . --no-sandbox "$@" } function code-wsl() From bb04e412a9c2ce9be4c68cecc64a09c44271d592 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 17 Feb 2020 16:38:27 +0100 Subject: [PATCH 109/109] Fixes #90391: Split lines into spans of at most 16384 characters --- .../browser/view/domLineBreaksComputer.ts | 36 +++++++++++++------ 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/src/vs/editor/browser/view/domLineBreaksComputer.ts b/src/vs/editor/browser/view/domLineBreaksComputer.ts index 600f303335e4..81611253b015 100644 --- a/src/vs/editor/browser/view/domLineBreaksComputer.ts +++ b/src/vs/editor/browser/view/domLineBreaksComputer.ts @@ -149,6 +149,10 @@ function createLineBreaks(requests: string[], fontInfo: FontInfo, tabSize: numbe return result; } +const enum Constants { + SPAN_MODULO_LIMIT = 16384 +} + function renderLine(lineContent: string, initialVisibleColumn: number, tabSize: number, width: number, sb: IStringBuilder): [number[], number[]] { sb.appendASCIIString('