From 660cbca5e148b01b1f4ad6c4c305a135b2f2c37f Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 8 Jul 2021 21:41:26 +0200 Subject: [PATCH 01/66] Sets mightBeForeignElement to true when mouse is on injected text. --- .../editor/browser/controller/mouseTarget.ts | 27 +++++++++------- .../common/viewModel/splitLinesCollection.ts | 32 ++++++++++++++++++- src/vs/editor/common/viewModel/viewModel.ts | 21 ++++++++++-- .../editor/common/viewModel/viewModelImpl.ts | 6 +++- 4 files changed, 70 insertions(+), 16 deletions(-) diff --git a/src/vs/editor/browser/controller/mouseTarget.ts b/src/vs/editor/browser/controller/mouseTarget.ts index 41ad643ecc3..1888d01971c 100644 --- a/src/vs/editor/browser/controller/mouseTarget.ts +++ b/src/vs/editor/browser/controller/mouseTarget.ts @@ -14,7 +14,7 @@ import { Position } from 'vs/editor/common/core/position'; import { Range as EditorRange } from 'vs/editor/common/core/range'; import { HorizontalPosition } from 'vs/editor/common/view/renderingContext'; import { ViewContext } from 'vs/editor/common/view/viewContext'; -import { IViewModel } from 'vs/editor/common/viewModel/viewModel'; +import { InjectedText, IViewModel } from 'vs/editor/common/viewModel/viewModel'; import { CursorColumns } from 'vs/editor/common/controller/cursorCommon'; import * as dom from 'vs/base/browser/dom'; import { AtomicTabMoveOperations, Direction } from 'vs/editor/common/controller/cursorAtomicMoveOperations'; @@ -61,7 +61,8 @@ class ContentHitTestResult { readonly type = HitTestResultType.Content; constructor( readonly position: Position, - readonly spanNode: HTMLElement + readonly spanNode: HTMLElement, + readonly injectedText: InjectedText | null, ) { } } @@ -71,7 +72,7 @@ namespace HitTestResult { export function createFromDOMInfo(ctx: HitTestContext, spanNode: HTMLElement, offset: number): HitTestResult { const position = ctx.getPositionFromDOMInfo(spanNode, offset); if (position) { - return new ContentHitTestResult(position, spanNode); + return new ContentHitTestResult(position, spanNode, null); } return new UnknownHitTestResult(spanNode); } @@ -505,7 +506,7 @@ export class MouseTargetFactory { const hitTestResult = MouseTargetFactory._doHitTest(ctx, request); if (hitTestResult.type === HitTestResultType.Content) { - return MouseTargetFactory.createMouseTargetFromHitTestPosition(ctx, request, hitTestResult.spanNode, hitTestResult.position); + return MouseTargetFactory.createMouseTargetFromHitTestPosition(ctx, request, hitTestResult.spanNode, hitTestResult.position, hitTestResult.injectedText); } return this._createMouseTarget(ctx, request.withTarget(hitTestResult.hitTarget), true); @@ -702,7 +703,7 @@ export class MouseTargetFactory { const hitTestResult = MouseTargetFactory._doHitTest(ctx, request); if (hitTestResult.type === HitTestResultType.Content) { - return MouseTargetFactory.createMouseTargetFromHitTestPosition(ctx, request, hitTestResult.spanNode, hitTestResult.position); + return MouseTargetFactory.createMouseTargetFromHitTestPosition(ctx, request, hitTestResult.spanNode, hitTestResult.position, hitTestResult.injectedText); } return this._createMouseTarget(ctx, request.withTarget(hitTestResult.hitTarget), true); @@ -758,7 +759,7 @@ export class MouseTargetFactory { return (chars + 1); } - private static createMouseTargetFromHitTestPosition(ctx: HitTestContext, request: HitTestRequest, spanNode: HTMLElement, pos: Position): MouseTarget { + private static createMouseTargetFromHitTestPosition(ctx: HitTestContext, request: HitTestRequest, spanNode: HTMLElement, pos: Position, injectedText: InjectedText | null): MouseTarget { const lineNumber = pos.lineNumber; const column = pos.column; @@ -778,7 +779,7 @@ export class MouseTargetFactory { const columnHorizontalOffset = visibleRange.left; if (request.mouseContentHorizontalOffset === columnHorizontalOffset) { - return request.fulfill(MouseTargetType.CONTENT_TEXT, pos, null, { mightBeForeignElement: false }); + return request.fulfill(MouseTargetType.CONTENT_TEXT, pos, null, { mightBeForeignElement: !!injectedText }); } // Let's define a, b, c and check if the offset is in between them... @@ -811,10 +812,10 @@ export class MouseTargetFactory { const curr = points[i]; if (prev.offset <= request.mouseContentHorizontalOffset && request.mouseContentHorizontalOffset <= curr.offset) { const rng = new EditorRange(lineNumber, prev.column, lineNumber, curr.column); - return request.fulfill(MouseTargetType.CONTENT_TEXT, pos, rng, { mightBeForeignElement: !mouseIsOverSpanNode }); + return request.fulfill(MouseTargetType.CONTENT_TEXT, pos, rng, { mightBeForeignElement: !mouseIsOverSpanNode || !!injectedText }); } } - return request.fulfill(MouseTargetType.CONTENT_TEXT, pos, null, { mightBeForeignElement: !mouseIsOverSpanNode }); + return request.fulfill(MouseTargetType.CONTENT_TEXT, pos, null, { mightBeForeignElement: !mouseIsOverSpanNode || !!injectedText }); } /** @@ -957,14 +958,16 @@ export class MouseTargetFactory { result = this._doHitTestWithCaretPositionFromPoint(ctx, request.pos.toClientCoordinates()); } if (result.type === HitTestResultType.Content) { + const injectedText = ctx.model.getInjectedTextAt(result.position); + const normalizedPosition = ctx.model.normalizePosition(result.position, PositionAffinity.None); - if (!normalizedPosition.equals(result.position)) { - result = new ContentHitTestResult(normalizedPosition, result.spanNode); + if (injectedText || !normalizedPosition.equals(result.position)) { + result = new ContentHitTestResult(normalizedPosition, result.spanNode, injectedText); } } // Snap to the nearest soft tab boundary if atomic soft tabs are enabled. if (result.type === HitTestResultType.Content && ctx.stickyTabStops) { - result = new ContentHitTestResult(this._snapToSoftTabBoundary(result.position, ctx.model), result.spanNode); + result = new ContentHitTestResult(this._snapToSoftTabBoundary(result.position, ctx.model), result.spanNode, result.injectedText); } return result; } diff --git a/src/vs/editor/common/viewModel/splitLinesCollection.ts b/src/vs/editor/common/viewModel/splitLinesCollection.ts index 1010899cc61..eb90c450f8e 100644 --- a/src/vs/editor/common/viewModel/splitLinesCollection.ts +++ b/src/vs/editor/common/viewModel/splitLinesCollection.ts @@ -12,7 +12,7 @@ import { EndOfLinePreference, IActiveIndentGuideInfo, IModelDecoration, IModelDe import { ModelDecorationOptions, ModelDecorationOverviewRulerOptions } from 'vs/editor/common/model/textModel'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { PrefixSumIndexOfResult } from 'vs/editor/common/viewModel/prefixSumComputer'; -import { ICoordinatesConverter, ILineBreaksComputer, IOverviewRulerDecorations, LineBreakData, SingleLineInlineDecoration, ViewLineData } from 'vs/editor/common/viewModel/viewModel'; +import { ICoordinatesConverter, InjectedText, ILineBreaksComputer, IOverviewRulerDecorations, LineBreakData, SingleLineInlineDecoration, ViewLineData } from 'vs/editor/common/viewModel/viewModel'; import { IDisposable } from 'vs/base/common/lifecycle'; import { FontInfo } from 'vs/editor/common/config/fontInfo'; import { EditorTheme } from 'vs/editor/common/view/viewContext'; @@ -48,6 +48,8 @@ export interface ISplitLine { getViewPositionOfModelPosition(deltaLineNumber: number, inputColumn: number, affinity?: PositionAffinity): Position; getViewLineNumberOfModelPosition(deltaLineNumber: number, inputColumn: number): number; normalizePosition(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number, outputPosition: Position, affinity: PositionAffinity): Position; + + getInjectedTextAt(outputLineIndex: number, column: number): InjectedText | null; } export interface IViewModelLinesCollection extends IDisposable { @@ -78,6 +80,8 @@ export interface IViewModelLinesCollection extends IDisposable { getAllOverviewRulerDecorations(ownerId: number, filterOutValidation: boolean, theme: EditorTheme): IOverviewRulerDecorations; getDecorationsInRange(range: Range, ownerId: number, filterOutValidation: boolean): IModelDecoration[]; + getInjectedTextAt(viewPosition: Position): InjectedText | null; + normalizePosition(position: Position, affinity: PositionAffinity): Position; /** * Gets the column at which indentation stops at a given line. @@ -997,6 +1001,15 @@ export class SplitLinesCollection implements IViewModelLinesCollection { return finalResult; } + public getInjectedTextAt(position: Position): InjectedText | null { + const viewLineNumber = this._toValidViewLineNumber(position.lineNumber); + const r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1); + const lineIndex = r.index; + const remainder = r.remainder; + + return this.lines[lineIndex].getInjectedTextAt(remainder, position.column); + } + normalizePosition(position: Position, affinity: PositionAffinity): Position { const viewLineNumber = this._toValidViewLineNumber(position.lineNumber); const r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1); @@ -1101,6 +1114,10 @@ class VisibleIdentitySplitLine implements ISplitLine { public normalizePosition(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number, outputPosition: Position, affinity: PositionAffinity): Position { return outputPosition; } + + public getInjectedTextAt(_outputLineIndex: number, _outputColumn: number): InjectedText | null { + return null; + } } class InvisibleIdentitySplitLine implements ISplitLine { @@ -1167,6 +1184,10 @@ class InvisibleIdentitySplitLine implements ISplitLine { public normalizePosition(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number, outputPosition: Position, affinity: PositionAffinity): Position { throw new Error('Not supported'); } + + public getInjectedTextAt(_outputLineIndex: number, _outputColumn: number): InjectedText | null { + throw new Error('Not supported'); + } } export class SplitLine implements ISplitLine { @@ -1451,6 +1472,10 @@ export class SplitLine implements ISplitLine { return outputPosition; } + + public getInjectedTextAt(outputLineIndex: number, outputColumn: number): InjectedText | null { + return this._lineBreakData.getInjectedText(outputLineIndex, outputColumn); + } } let _spaces: string[] = ['']; @@ -1694,6 +1719,11 @@ export class IdentityLinesCollection implements IViewModelLinesCollection { public getLineIndentColumn(lineNumber: number): number { return this.model.getLineIndentColumn(lineNumber); } + + public getInjectedTextAt(position: Position): InjectedText | null { + // Identity lines collection does not support injected text. + return null; + } } class OverviewRulerDecorations { diff --git a/src/vs/editor/common/viewModel/viewModel.ts b/src/vs/editor/common/viewModel/viewModel.ts index 3b4825d1c1d..97feb61ba9c 100644 --- a/src/vs/editor/common/viewModel/viewModel.ts +++ b/src/vs/editor/common/viewModel/viewModel.ts @@ -205,7 +205,7 @@ export class LineBreakData { } public normalizeOffsetAroundInjections(offsetInUnwrappedLine: number, affinity: PositionAffinity): number { - const injectedText = this.getInjectedTextAt(offsetInUnwrappedLine); + const injectedText = this.getInjectedTextAtOffset(offsetInUnwrappedLine); if (!injectedText) { return offsetInUnwrappedLine; } @@ -242,7 +242,18 @@ export class LineBreakData { return result; } - private getInjectedTextAt(offsetInUnwrappedLine: number): { injectedTextIndex: number, offsetInUnwrappedLine: number, length: number } | undefined { + public getInjectedText(outputLineIndex: number, outputOffset: number): InjectedText | null { + const offset = this.outputPositionToOffsetInUnwrappedLine(outputLineIndex, outputOffset); + const injectedText = this.getInjectedTextAtOffset(offset); + if (!injectedText) { + return null; + } + return { + options: this.injectionOptions![injectedText.injectedTextIndex] + }; + } + + private getInjectedTextAtOffset(offsetInUnwrappedLine: number): { injectedTextIndex: number, offsetInUnwrappedLine: number, length: number } | undefined { const injectionOffsets = this.injectionOffsets; const injectionOptions = this.injectionOptions; @@ -328,6 +339,8 @@ export interface IViewModel extends ICursorSimpleModel { invalidateMinimapColorCache(): void; getValueInRange(range: Range, eol: EndOfLinePreference): string; + getInjectedTextAt(viewPosition: Position): InjectedText | null; + getModelLineMaxColumn(modelLineNumber: number): number; validateModelPosition(modelPosition: IPosition): Position; validateModelRange(range: IRange): Range; @@ -372,6 +385,10 @@ export interface IViewModel extends ICursorSimpleModel { //#endregion } +export class InjectedText { + constructor(public readonly options: InjectedTextOptions) { } +} + export class MinimapLinesRenderingData { public readonly tabSize: number; public readonly data: Array; diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index dc410ae2ca9..5af54d6c470 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -21,7 +21,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, ILineBreaksComputer, IOverviewRulerDecorations, IViewModel, MinimapLinesRenderingData, ViewLineData, ViewLineRenderingData, ViewModelDecoration } from 'vs/editor/common/viewModel/viewModel'; +import { ICoordinatesConverter, InjectedText, ILineBreaksComputer, 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'; @@ -652,6 +652,10 @@ export class ViewModel extends Disposable implements IViewModel { return this._decorations.getDecorationsViewportData(visibleRange).decorations; } + public getInjectedTextAt(viewPosition: Position): InjectedText | null { + return this._lines.getInjectedTextAt(viewPosition); + } + public getViewLineRenderingData(visibleRange: Range, lineNumber: number): ViewLineRenderingData { let mightContainRTL = this.model.mightContainRTL(); let mightContainNonBasicASCII = this.model.mightContainNonBasicASCII(); From 33ea98830e8cc33737bd76d9724b285c7fccac0c Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 8 Jul 2021 21:59:26 +0200 Subject: [PATCH 02/66] When converting an empty model range to a view range, it should prefer left view positions. This is important when hover ranges are translated. --- .../editor/common/viewModel/splitLinesCollection.ts | 13 +++++++------ .../common/viewModel/viewModelDecorations.test.ts | 8 ++++---- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/vs/editor/common/viewModel/splitLinesCollection.ts b/src/vs/editor/common/viewModel/splitLinesCollection.ts index 1010899cc61..d2bf36b7db5 100644 --- a/src/vs/editor/common/viewModel/splitLinesCollection.ts +++ b/src/vs/editor/common/viewModel/splitLinesCollection.ts @@ -883,13 +883,14 @@ export class SplitLinesCollection implements IViewModelLinesCollection { } public convertModelRangeToViewRange(modelRange: Range): Range { - const start = this.convertModelPositionToViewPosition(modelRange.startLineNumber, modelRange.startColumn, PositionAffinity.Right); - let end = this.convertModelPositionToViewPosition(modelRange.endLineNumber, modelRange.endColumn, PositionAffinity.Left); - if (end.isBefore(start)) { - // If the range is empty, we don't want the range to get expanded just by converting to a view range - end = start; + if (modelRange.isEmpty()) { + const start = this.convertModelPositionToViewPosition(modelRange.startLineNumber, modelRange.startColumn, PositionAffinity.Left); + return Range.fromPositions(start); + } else { + const start = this.convertModelPositionToViewPosition(modelRange.startLineNumber, modelRange.startColumn, PositionAffinity.Right); + const end = this.convertModelPositionToViewPosition(modelRange.endLineNumber, modelRange.endColumn, PositionAffinity.Left); + return new Range(start.lineNumber, start.column, end.lineNumber, end.column); } - return new Range(start.lineNumber, start.column, end.lineNumber, end.column); } private _getViewLineNumberForModelPosition(inputLineNumber: number, inputColumn: number): number { diff --git a/src/vs/editor/test/common/viewModel/viewModelDecorations.test.ts b/src/vs/editor/test/common/viewModel/viewModelDecorations.test.ts index f0b0996b5b4..e1b5683eabb 100644 --- a/src/vs/editor/test/common/viewModel/viewModelDecorations.test.ts +++ b/src/vs/editor/test/common/viewModel/viewModelDecorations.test.ts @@ -49,7 +49,7 @@ suite('ViewModelDecorations', () => { // starts before viewport, ends after viewport accessor.addDecoration(new Range(1, 2, 1, 51), createOpts('dec5')); - // starts at viewport start, ends at viewport start + // starts at viewport start, ends at viewport start (will not be visible on view line 2) accessor.addDecoration(new Range(1, 14, 1, 14), createOpts('dec6')); // starts at viewport start, ends inside viewport accessor.addDecoration(new Range(1, 14, 1, 16), createOpts('dec7')); @@ -108,9 +108,6 @@ suite('ViewModelDecorations', () => { new InlineDecoration(new Range(2, 2, 2, 2), 'a-dec3', InlineDecorationType.After), new InlineDecoration(new Range(1, 2, 3, 13), 'i-dec4', InlineDecorationType.Regular), new InlineDecoration(new Range(1, 2, 5, 8), 'i-dec5', InlineDecorationType.Regular), - new InlineDecoration(new Range(2, 1, 2, 1), 'i-dec6', InlineDecorationType.Regular), - new InlineDecoration(new Range(2, 1, 2, 1), 'b-dec6', InlineDecorationType.Before), - new InlineDecoration(new Range(2, 1, 2, 1), 'a-dec6', InlineDecorationType.After), new InlineDecoration(new Range(2, 1, 2, 3), 'i-dec7', InlineDecorationType.Regular), new InlineDecoration(new Range(2, 1, 2, 1), 'b-dec7', InlineDecorationType.Before), new InlineDecoration(new Range(2, 3, 2, 3), 'a-dec7', InlineDecorationType.After), @@ -143,6 +140,9 @@ suite('ViewModelDecorations', () => { new InlineDecoration(new Range(2, 3, 3, 13), 'i-dec11', InlineDecorationType.Regular), new InlineDecoration(new Range(3, 13, 3, 13), 'a-dec11', InlineDecorationType.After), new InlineDecoration(new Range(2, 3, 5, 8), 'i-dec12', InlineDecorationType.Regular), + new InlineDecoration(new Range(3, 13, 3, 13), 'i-dec13', InlineDecorationType.Regular), + new InlineDecoration(new Range(3, 13, 3, 13), 'b-dec13', InlineDecorationType.Before), + new InlineDecoration(new Range(3, 13, 3, 13), 'a-dec13', InlineDecorationType.After), ]); }); }); From 2970e66828cbc74613cbdeaaa4cd546173485d0e Mon Sep 17 00:00:00 2001 From: Jean Pierre Date: Fri, 9 Jul 2021 03:27:36 -0500 Subject: [PATCH 03/66] Fixes #128268 (#128284) --- src/vs/base/browser/ui/list/listWidget.ts | 4 ++-- src/vs/platform/list/browser/listService.ts | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 68387c77d50..13c5cef9511 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -281,7 +281,7 @@ class KeyboardController implements IDisposable { this.onKeyDown.filter(e => e.keyCode === KeyCode.PageDown).on(this.onPageDownArrow, this, this.disposables); this.onKeyDown.filter(e => e.keyCode === KeyCode.Escape).on(this.onEscape, this, this.disposables); - if (options.multipleSelectionSupport) { + if (options.multipleSelectionSupport !== false) { this.onKeyDown.filter(e => (platform.isMacintosh ? e.metaKey : e.ctrlKey) && e.keyCode === KeyCode.KEY_A).on(this.onCtrlA, this, this.multipleSelectionDisposables); } } @@ -1346,7 +1346,7 @@ export class List implements ISpliceable, IThemable, IDisposable { this.ariaLabel = this.accessibilityProvider.getWidgetAriaLabel(); } - if (this._options.multipleSelectionSupport) { + if (this._options.multipleSelectionSupport !== false) { this.view.domNode.setAttribute('aria-multiselectable', 'true'); } } diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index ec3b0dc72e1..550827d2b03 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -240,7 +240,7 @@ export class WorkbenchList extends List { this.themeService = themeService; this.listSupportsMultiSelect = WorkbenchListSupportsMultiSelectContextKey.bindTo(this.contextKeyService); - this.listSupportsMultiSelect.set(!!options.multipleSelectionSupport); + this.listSupportsMultiSelect.set(options.multipleSelectionSupport !== false); const listSelectionNavigation = WorkbenchListSelectionNavigation.bindTo(this.contextKeyService); listSelectionNavigation.set(Boolean(options.selectionNavigation)); @@ -374,7 +374,7 @@ export class WorkbenchPagedList extends PagedList { this.horizontalScrolling = options.horizontalScrolling; this.listSupportsMultiSelect = WorkbenchListSupportsMultiSelectContextKey.bindTo(this.contextKeyService); - this.listSupportsMultiSelect.set(!!options.multipleSelectionSupport); + this.listSupportsMultiSelect.set(options.multipleSelectionSupport !== false); const listSelectionNavigation = WorkbenchListSelectionNavigation.bindTo(this.contextKeyService); listSelectionNavigation.set(Boolean(options.selectionNavigation)); @@ -499,7 +499,7 @@ export class WorkbenchTable extends Table { this.themeService = themeService; this.listSupportsMultiSelect = WorkbenchListSupportsMultiSelectContextKey.bindTo(this.contextKeyService); - this.listSupportsMultiSelect.set(!!options.multipleSelectionSupport); + this.listSupportsMultiSelect.set(options.multipleSelectionSupport !== false); const listSelectionNavigation = WorkbenchListSelectionNavigation.bindTo(this.contextKeyService); listSelectionNavigation.set(Boolean(options.selectionNavigation)); @@ -1099,7 +1099,7 @@ class WorkbenchTreeInternals { this.contextKeyService = createScopedContextKeyService(contextKeyService, tree); this.listSupportsMultiSelect = WorkbenchListSupportsMultiSelectContextKey.bindTo(this.contextKeyService); - this.listSupportsMultiSelect.set(!!options.multipleSelectionSupport); + this.listSupportsMultiSelect.set(options.multipleSelectionSupport !== false); const listSelectionNavigation = WorkbenchListSelectionNavigation.bindTo(this.contextKeyService); listSelectionNavigation.set(Boolean(options.selectionNavigation)); From a6f056e1d9b2171b493201cd6be32861bf54c12d Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 9 Jul 2021 12:10:46 +0200 Subject: [PATCH 04/66] Fixes #128302. --- src/vs/editor/contrib/inlineCompletions/ghostText.ts | 2 +- src/vs/editor/contrib/inlineCompletions/ghostTextWidget.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/ghostText.ts b/src/vs/editor/contrib/inlineCompletions/ghostText.ts index 178431c7b2a..f39df6d80c9 100644 --- a/src/vs/editor/contrib/inlineCompletions/ghostText.ts +++ b/src/vs/editor/contrib/inlineCompletions/ghostText.ts @@ -30,7 +30,7 @@ export class GhostText { export class GhostTextPart { constructor( readonly column: number, - readonly lines: string[], + readonly lines: readonly string[], ) { } diff --git a/src/vs/editor/contrib/inlineCompletions/ghostTextWidget.ts b/src/vs/editor/contrib/inlineCompletions/ghostTextWidget.ts index cff08300855..5f4decb3058 100644 --- a/src/vs/editor/contrib/inlineCompletions/ghostTextWidget.ts +++ b/src/vs/editor/contrib/inlineCompletions/ghostTextWidget.ts @@ -86,7 +86,7 @@ export class GhostTextWidget extends Disposable { const inlineTexts = new Array(); const additionalLines = new Array(); - function addToAdditionalLines(lines: string[], className: string | undefined) { + function addToAdditionalLines(lines: readonly string[], className: string | undefined) { if (additionalLines.length > 0) { const lastLine = additionalLines[additionalLines.length - 1]; if (className) { @@ -94,7 +94,7 @@ export class GhostTextWidget extends Disposable { } lastLine.content += lines[0]; - lines.splice(0, 1); + lines = lines.slice(1); } for (const line of lines) { additionalLines.push({ From 60ddedc0503dfec6cb1aac0814f7fc09dc52d2b3 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 9 Jul 2021 12:15:06 +0200 Subject: [PATCH 05/66] Don't fire an on change if the ghost text model did not change. --- src/vs/editor/contrib/inlineCompletions/ghostTextModel.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/editor/contrib/inlineCompletions/ghostTextModel.ts b/src/vs/editor/contrib/inlineCompletions/ghostTextModel.ts index 25ff2d76792..96e004f85be 100644 --- a/src/vs/editor/contrib/inlineCompletions/ghostTextModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/ghostTextModel.ts @@ -27,6 +27,9 @@ export abstract class DelegatingModel extends Disposable implements GhostTextWid } protected setTargetModel(model: GhostTextWidgetModel | undefined): void { + if (this.currentModelRef.value?.object === model) { + return; + } this.currentModelRef.clear(); this.currentModelRef.value = model ? createDisposableRef(model, model.onDidChange(() => { this.hasCachedGhostText = false; From 203d39e79bd9c50d282edb02a7cfc674b3ed1e58 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 9 Jul 2021 05:29:09 -0700 Subject: [PATCH 06/66] Ctrl+w close active editor instead of disposing active terminal Fixes #128269 --- src/vs/workbench/contrib/terminal/browser/terminalActions.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 46fd746bd59..e03b17d1586 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -29,9 +29,10 @@ import { IPickOptions, IQuickInputService, IQuickPickItem } from 'vs/platform/qu import { ICreateTerminalOptions, ITerminalProfile, TerminalLocation, TerminalSettingId, TitleEventSource } from 'vs/platform/terminal/common/terminal'; import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands'; +import { CLOSE_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; import { ResourceContextKey } from 'vs/workbench/common/resources'; import { FindInFilesCommand, IFindInFilesArgs } from 'vs/workbench/contrib/search/browser/searchActions'; -import { Direction, IRemoteTerminalService, ITerminalEditorService, ITerminalGroupService, ITerminalInstance, ITerminalInstanceService, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { Direction, IRemoteTerminalService, ITerminalGroupService, ITerminalInstance, ITerminalInstanceService, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalQuickAccessProvider } from 'vs/workbench/contrib/terminal/browser/terminalQuickAccess'; import { ILocalTerminalService, IRemoteTerminalAttachTarget, ITerminalConfigHelper, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, KEYBINDING_CONTEXT_TERMINAL_ALT_BUFFER_ACTIVE, KEYBINDING_CONTEXT_TERMINAL_FIND_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_FIND_NOT_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FIND_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN, KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED, KEYBINDING_CONTEXT_TERMINAL_TABS_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TABS_SINGULAR_SELECTION, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TerminalCommandId, TERMINAL_ACTION_CATEGORY } from 'vs/workbench/contrib/terminal/common/terminal'; import { terminalStrings } from 'vs/workbench/contrib/terminal/common/terminalStrings'; @@ -1728,7 +1729,7 @@ export function registerTerminalActions() { }); } async run(accessor: ServicesAccessor) { - accessor.get(ITerminalEditorService).activeInstance?.dispose(); + accessor.get(ICommandService).executeCommand(CLOSE_EDITOR_COMMAND_ID); } }); From 7461a73887e56fd9b5894d44b7e81d14493eb52c Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 9 Jul 2021 14:33:31 +0200 Subject: [PATCH 07/66] Fixes column to offset conversion. --- src/vs/editor/common/viewModel/splitLinesCollection.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/common/viewModel/splitLinesCollection.ts b/src/vs/editor/common/viewModel/splitLinesCollection.ts index eb90c450f8e..cfa6c5448c1 100644 --- a/src/vs/editor/common/viewModel/splitLinesCollection.ts +++ b/src/vs/editor/common/viewModel/splitLinesCollection.ts @@ -1474,7 +1474,7 @@ export class SplitLine implements ISplitLine { } public getInjectedTextAt(outputLineIndex: number, outputColumn: number): InjectedText | null { - return this._lineBreakData.getInjectedText(outputLineIndex, outputColumn); + return this._lineBreakData.getInjectedText(outputLineIndex, outputColumn - 1); } } From 3bcc2b2d8e8811d53b77045abdea0a0b63ffc57e Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Fri, 9 Jul 2021 09:09:30 -0400 Subject: [PATCH 08/66] Bump distro --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cb6877f0e86..7d9ea7cf630 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.59.0", - "distro": "d3a0cc9d45a73d4486d9969408ce4b21da76844f", + "distro": "4f9399b417ee98731317fbd4616d6fc287172355", "author": { "name": "Microsoft Corporation" }, From af7e67f8e4a58a083445f294fb7abbb65151a2ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Fri, 9 Jul 2021 15:33:33 +0200 Subject: [PATCH 09/66] use esrpcli.dll --- build/azure-pipelines/common/sign-win32.ts | 17 ++ build/azure-pipelines/common/sign.ts | 84 +++++++++ .../darwin/product-build-darwin-sign.yml | 69 ++----- .../linux/product-build-linux.yml | 35 ++-- .../win32/ESRPClient/NuGet.config | 10 -- .../win32/ESRPClient/packages.config | 4 - .../azure-pipelines/win32/prepare-publish.ps1 | 3 - .../win32/product-build-win32.yml | 118 +++++------- build/azure-pipelines/win32/sign.ps1 | 71 -------- build/gulpfile.vscode.win32.js | 4 +- build/package.json | 5 + build/yarn.lock | 170 +++++++++++++++++- 12 files changed, 356 insertions(+), 234 deletions(-) create mode 100644 build/azure-pipelines/common/sign-win32.ts create mode 100644 build/azure-pipelines/common/sign.ts delete mode 100644 build/azure-pipelines/win32/ESRPClient/NuGet.config delete mode 100644 build/azure-pipelines/win32/ESRPClient/packages.config delete mode 100644 build/azure-pipelines/win32/sign.ps1 diff --git a/build/azure-pipelines/common/sign-win32.ts b/build/azure-pipelines/common/sign-win32.ts new file mode 100644 index 00000000000..d5daa875812 --- /dev/null +++ b/build/azure-pipelines/common/sign-win32.ts @@ -0,0 +1,17 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { main } from './sign'; +import * as path from 'path'; + +main([ + process.env['EsrpCliDllPath']!, + 'windows', + process.env['ESRPPKI']!, + process.env['ESRPAADUsername']!, + process.env['ESRPAADPassword']!, + path.dirname(process.argv[2]), + path.basename(process.argv[2]) +]); diff --git a/build/azure-pipelines/common/sign.ts b/build/azure-pipelines/common/sign.ts new file mode 100644 index 00000000000..e03682deb4b --- /dev/null +++ b/build/azure-pipelines/common/sign.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 * as cp from 'child_process'; +import * as fs from 'fs'; +import * as tmp from 'tmp'; +import * as crypto from 'crypto'; + +function getParams(type: string): string { + switch (type) { + case 'windows': + return '[{"keyCode":"CP-230012","operationSetCode":"SigntoolSign","parameters":[{"parameterName":"OpusName","parameterValue":"VS Code"},{"parameterName":"OpusInfo","parameterValue":"https://code.visualstudio.com/"},{"parameterName":"Append","parameterValue":"/as"},{"parameterName":"FileDigest","parameterValue":"/fd \\"SHA256\\""},{"parameterName":"PageHash","parameterValue":"/NPH"},{"parameterName":"TimeStamp","parameterValue":"/tr \\"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\\" /td sha256"}],"toolName":"sign","toolVersion":"1.0"},{"keyCode":"CP-230012","operationSetCode":"SigntoolVerify","parameters":[{"parameterName":"VerifyAll","parameterValue":"/all"}],"toolName":"sign","toolVersion":"1.0"}]'; + case 'rpm': + return '[{ "keyCode": "CP-450779-Pgp", "operationSetCode": "LinuxSign", "parameters": [], "toolName": "sign", "toolVersion": "1.0" }]'; + case 'darwin-sign': + return '[{"keyCode":"CP-401337-Apple","operationSetCode":"MacAppDeveloperSign","parameters":[{"parameterName":"Hardening","parameterValue":"--options=runtime"}],"toolName":"sign","toolVersion":"1.0"}]'; + case 'darwin-notarize': + return '[{"keyCode":"CP-401337-Apple","operationSetCode":"MacAppNotarize","parameters":[{"parameterName":"BundleId","parameterValue":"$(BundleIdentifier)"}],"toolName":"sign","toolVersion":"1.0"}]'; + default: + throw new Error(`Sign type ${type} not found`); + } +} + +export function main([esrpCliPath, type, cert, username, password, folderPath, pattern]: string[]) { + tmp.setGracefulCleanup(); + + const patternPath = tmp.tmpNameSync(); + fs.writeFileSync(patternPath, pattern); + + const paramsPath = tmp.tmpNameSync(); + fs.writeFileSync(paramsPath, getParams(type)); + + const keyFile = tmp.tmpNameSync(); + const key = crypto.randomBytes(32); + const iv = crypto.randomBytes(16); + fs.writeFileSync(keyFile, JSON.stringify({ key: key.toString('hex'), iv: iv.toString('hex') })); + + const clientkeyPath = tmp.tmpNameSync(); + const clientkeyCypher = crypto.createCipheriv('aes-256-cbc', key, iv); + let clientkey = clientkeyCypher.update(password, 'utf8', 'hex'); + clientkey += clientkeyCypher.final('hex'); + fs.writeFileSync(clientkeyPath, clientkey); + + const clientcertPath = tmp.tmpNameSync(); + const clientcertCypher = crypto.createCipheriv('aes-256-cbc', key, iv); + let clientcert = clientcertCypher.update(cert, 'utf8', 'hex'); + clientcert += clientcertCypher.final('hex'); + fs.writeFileSync(clientcertPath, clientcert); + + const args = [ + esrpCliPath, + 'vsts.sign', + '-a', username, + '-k', clientkeyPath, + '-z', clientcertPath, + '-f', folderPath, + '-p', patternPath, + '-u', 'false', + '-x', 'regularSigning', + '-b', 'input.json', + '-l', 'AzSecPack_PublisherPolicyProd.xml', + '-y', 'inlineSignParams', + '-j', paramsPath, + '-c', '9997', + '-t', '120', + '-g', '10', + '-v', 'Tls12', + '-s', 'https://api.esrp.microsoft.com/api/v1', + '-m', '0', + '-o', 'Microsoft', + '-i', 'https://www.microsoft.com', + '-n', '5', + '-r', 'true', + '-e', keyFile, + ]; + + cp.spawnSync('dotnet', args, { stdio: 'inherit' }); +} + +if (require.main === module) { + main(process.argv.slice(2)); +} diff --git a/build/azure-pipelines/darwin/product-build-darwin-sign.yml b/build/azure-pipelines/darwin/product-build-darwin-sign.yml index 05ba689a4cb..8b5dd741b51 100644 --- a/build/azure-pipelines/darwin/product-build-darwin-sign.yml +++ b/build/azure-pipelines/darwin/product-build-darwin-sign.yml @@ -8,7 +8,7 @@ steps: inputs: azureSubscription: "vscode-builds-subscription" KeyVaultName: vscode - SecretsFilter: 'github-distro-mixin-password' + SecretsFilter: "github-distro-mixin-password,ESRP-PKI,esrp-aad-username,esrp-aad-password" - script: | set -e @@ -28,12 +28,10 @@ steps: displayName: Merge distro - script: | - pushd build \ - && yarn \ - && npm install -g typescript \ - && tsc azure-pipelines/common/createAsset.ts \ - && popd - displayName: Restore modules for just build folder and compile it + set -e + yarn --cwd build + yarn --cwd build compile + displayName: Compile build tools - download: current artifact: unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive @@ -45,28 +43,16 @@ steps: mv $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-$(VSCODE_ARCH).zip $(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH).zip displayName: Unzip & move - - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1 + - task: UseDotNet@2 inputs: - ConnectedServiceName: "ESRP CodeSign" - FolderPath: "$(agent.builddirectory)" - Pattern: "VSCode-darwin-$(VSCODE_ARCH).zip" - signConfigType: inlineSignParams - inlineOperation: | - [ - { - "keyCode": "CP-401337-Apple", - "operationSetCode": "MacAppDeveloperSign", - "parameters": [ - { - "parameterName": "Hardening", - "parameterValue": "--options=runtime" - } - ], - "toolName": "sign", - "toolVersion": "1.0" - } - ] - SessionTimeout: 60 + version: 2.x + + - task: EsrpClientTool@1 + displayName: Download ESRPClient + + - script: | + set -e + node build/azure-pipelines/common/sign "$(esrpclient.toolpath)/$(esrpclient.toolname)" darwin-sign $(ESRP-PKI) $(esrp-aad-username) $(esrp-aad-password) $(agent.builddirectory) VSCode-darwin-$(VSCODE_ARCH).zip displayName: Codesign - script: | @@ -76,29 +62,10 @@ steps: echo "##vso[task.setvariable variable=BundleIdentifier]$BUNDLE_IDENTIFIER" displayName: Export bundle identifier - - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1 - inputs: - ConnectedServiceName: "ESRP CodeSign" - FolderPath: "$(agent.builddirectory)" - Pattern: "VSCode-darwin-$(VSCODE_ARCH).zip" - signConfigType: inlineSignParams - inlineOperation: | - [ - { - "keyCode": "CP-401337-Apple", - "operationSetCode": "MacAppNotarize", - "parameters": [ - { - "parameterName": "BundleId", - "parameterValue": "$(BundleIdentifier)" - } - ], - "toolName": "sign", - "toolVersion": "1.0" - } - ] - SessionTimeout: 60 - displayName: Notarization + - script: | + set -e + node build/azure-pipelines/common/sign "$(esrpclient.toolpath)/$(esrpclient.toolname)" darwin-notarize $(ESRP-PKI) $(esrp-aad-username) $(esrp-aad-password) $(agent.builddirectory) VSCode-darwin-$(VSCODE_ARCH).zip + displayName: Notarize - script: | set -e diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index 408ffa3237a..6a2262330a0 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -12,7 +12,7 @@ steps: inputs: azureSubscription: "vscode-builds-subscription" KeyVaultName: vscode - SecretsFilter: 'github-distro-mixin-password,builds-docdb-key-readwrite,vscode-storage-key' + SecretsFilter: "github-distro-mixin-password,builds-docdb-key-readwrite,vscode-storage-key,ESRP-PKI,esrp-aad-username,esrp-aad-password" - task: DownloadPipelineArtifact@2 inputs: @@ -252,30 +252,25 @@ steps: displayName: Prepare snap package condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false')) - # needed for code signing - task: UseDotNet@2 - displayName: "Install .NET Core SDK 2.x" inputs: version: 2.x condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false')) - - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1 - inputs: - ConnectedServiceName: "ESRP CodeSign" - FolderPath: ".build/linux/rpm" - Pattern: "*.rpm" - signConfigType: inlineSignParams - inlineOperation: | - [ - { - "keyCode": "CP-450779-Pgp", - "operationSetCode": "LinuxSign", - "parameters": [ ], - "toolName": "sign", - "toolVersion": "1.0" - } - ] - SessionTimeout: 120 + - task: EsrpClientTool@1 + displayName: Download ESRPClient + condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false')) + + - script: | + set -e + yarn --cwd build + yarn --cwd build compile + displayName: Compile build tools + condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false')) + + - script: | + set -e + node build/azure-pipelines/common/sign "$(esrpclient.toolpath)/$(esrpclient.toolname)" rpm $(ESRP-PKI) $(esrp-aad-username) $(esrp-aad-password) .build/linux/rpm '*.rpm' displayName: Codesign rpm condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false')) diff --git a/build/azure-pipelines/win32/ESRPClient/NuGet.config b/build/azure-pipelines/win32/ESRPClient/NuGet.config deleted file mode 100644 index 4ef337fa560..00000000000 --- a/build/azure-pipelines/win32/ESRPClient/NuGet.config +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/build/azure-pipelines/win32/ESRPClient/packages.config b/build/azure-pipelines/win32/ESRPClient/packages.config deleted file mode 100644 index ef586de9762..00000000000 --- a/build/azure-pipelines/win32/ESRPClient/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/build/azure-pipelines/win32/prepare-publish.ps1 b/build/azure-pipelines/win32/prepare-publish.ps1 index f80e1ca0ce9..d2870fe7871 100644 --- a/build/azure-pipelines/win32/prepare-publish.ps1 +++ b/build/azure-pipelines/win32/prepare-publish.ps1 @@ -2,9 +2,6 @@ $ErrorActionPreference = "Stop" $Arch = "$env:VSCODE_ARCH" - -exec { yarn gulp "vscode-win32-$Arch-archive" "vscode-win32-$Arch-system-setup" "vscode-win32-$Arch-user-setup" --sign } - $Repo = "$(pwd)" $Root = "$Repo\.." $SystemExe = "$Repo\.build\win32-$Arch\system-setup\VSCodeSetup.exe" diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index 1331338efbe..6a5bf78daf3 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -17,7 +17,7 @@ steps: inputs: azureSubscription: "vscode-builds-subscription" KeyVaultName: vscode - SecretsFilter: "github-distro-mixin-password,ESRP-SSL-AADAuth,vscode-storage-key,builds-docdb-key-readwrite" + SecretsFilter: "github-distro-mixin-password,vscode-storage-key,builds-docdb-key-readwrite,ESRP-PKI,esrp-aad-username,esrp-aad-password" - task: DownloadPipelineArtifact@2 inputs: @@ -247,84 +247,58 @@ steps: searchFolder: "$(Build.ArtifactStagingDirectory)/test-results" condition: and(succeededOrFailed(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64')) - - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1 + - task: UseDotNet@2 inputs: - ConnectedServiceName: "ESRP CodeSign" - FolderPath: "$(CodeSigningFolderPath)" - Pattern: "*.dll,*.exe,*.node" - signConfigType: inlineSignParams - inlineOperation: | - [ - { - "keyCode": "CP-230012", - "operationSetCode": "SigntoolSign", - "parameters": [ - { - "parameterName": "OpusName", - "parameterValue": "VS Code" - }, - { - "parameterName": "OpusInfo", - "parameterValue": "https://code.visualstudio.com/" - }, - { - "parameterName": "Append", - "parameterValue": "/as" - }, - { - "parameterName": "FileDigest", - "parameterValue": "/fd \"SHA256\"" - }, - { - "parameterName": "PageHash", - "parameterValue": "/NPH" - }, - { - "parameterName": "TimeStamp", - "parameterValue": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256" - } - ], - "toolName": "sign", - "toolVersion": "1.0" - }, - { - "keyCode": "CP-230012", - "operationSetCode": "SigntoolVerify", - "parameters": [ - { - "parameterName": "VerifyAll", - "parameterValue": "/all" - } - ], - "toolName": "sign", - "toolVersion": "1.0" - } - ] - SessionTimeout: 120 + version: 2.x condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false')) - - task: NuGetCommand@2 - displayName: Install ESRPClient.exe - inputs: - restoreSolution: 'build\azure-pipelines\win32\ESRPClient\packages.config' - feedsToUse: config - nugetConfigPath: 'build\azure-pipelines\win32\ESRPClient\NuGet.config' - externalFeedCredentials: "ESRP Nuget" - restoreDirectory: packages + - task: EsrpClientTool@1 + displayName: Download ESRPClient condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false')) - - task: ESRPImportCertTask@1 - displayName: Import ESRP Request Signing Certificate - inputs: - ESRP: "ESRP CodeSign" + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { yarn --cwd build } + exec { yarn --cwd build compile } + displayName: Compile build tools condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false')) - - task: PowerShell@2 - inputs: - targetType: filePath - filePath: .\build\azure-pipelines\win32\import-esrp-auth-cert.ps1 - arguments: "$(ESRP-SSL-AADAuth)" - displayName: Import ESRP Auth Certificate + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $EsrpClientTool = (gci -directory -filter EsrpClientTool_* $(Agent.RootDirectory)\_tasks | Select-Object -last 1).FullName + $EsrpCliZip = (gci -recurse -filter esrpcli.*.zip $EsrpClientTool | Select-Object -last 1).FullName + mkdir -p $(Agent.TempDirectory)\esrpcli + Expand-Archive -Path $EsrpCliZip -DestinationPath $(Agent.TempDirectory)\esrpcli + $EsrpCliDllPath = (gci -recurse -filter esrpcli.dll $(Agent.TempDirectory)\esrpcli | Select-Object -last 1).FullName + echo "##vso[task.setvariable variable=EsrpCliDllPath]$EsrpCliDllPath" + displayName: Find ESRP CLI + condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false')) + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { node build\azure-pipelines\common\sign $env:EsrpCliDllPath windows $(ESRP-PKI) $(esrp-aad-username) $(esrp-aad-password) $(CodeSigningFolderPath) '*.dll,*.exe,*.node' } + displayName: Codesign + condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false')) + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { yarn gulp "vscode-win32-$(VSCODE_ARCH)-archive" } + displayName: Package archive + condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false')) + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $env:ESRPPKI = "$(ESRP-PKI)" + $env:ESRPAADUsername = "$(esrp-aad-username)" + $env:ESRPAADPassword = "$(esrp-aad-password)" + exec { yarn gulp "vscode-win32-$(VSCODE_ARCH)-system-setup" --sign } + exec { yarn gulp "vscode-win32-$(VSCODE_ARCH)-user-setup" --sign } + displayName: Package setups condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false')) - powershell: | diff --git a/build/azure-pipelines/win32/sign.ps1 b/build/azure-pipelines/win32/sign.ps1 deleted file mode 100644 index b73db31207f..00000000000 --- a/build/azure-pipelines/win32/sign.ps1 +++ /dev/null @@ -1,71 +0,0 @@ -function Create-TmpJson($Obj) { - $FileName = [System.IO.Path]::GetTempFileName() - ConvertTo-Json -Depth 100 $Obj | Out-File -Encoding UTF8 $FileName - return $FileName -} - -$Auth = Create-TmpJson @{ - Version = "1.0.0" - AuthenticationType = "AAD_CERT" - ClientId = $env:ESRPClientId - AuthCert = @{ - SubjectName = $env:ESRPAuthCertificateSubjectName - StoreLocation = "LocalMachine" - StoreName = "My" - SendX5c = "true" - } - RequestSigningCert = @{ - SubjectName = $env:ESRPCertificateSubjectName - StoreLocation = "LocalMachine" - StoreName = "My" - } -} - -$Policy = Create-TmpJson @{ - Version = "1.0.0" -} - -$Input = Create-TmpJson @{ - Version = "1.0.0" - SignBatches = @( - @{ - SourceLocationType = "UNC" - SignRequestFiles = @( - @{ - SourceLocation = $args[0] - } - ) - SigningInfo = @{ - Operations = @( - @{ - KeyCode = "CP-230012" - OperationCode = "SigntoolSign" - Parameters = @{ - OpusName = "VS Code" - OpusInfo = "https://code.visualstudio.com/" - Append = "/as" - FileDigest = "/fd `"SHA256`"" - PageHash = "/NPH" - TimeStamp = "/tr `"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer`" /td sha256" - } - ToolName = "sign" - ToolVersion = "1.0" - }, - @{ - KeyCode = "CP-230012" - OperationCode = "SigntoolVerify" - Parameters = @{ - VerifyAll = "/all" - } - ToolName = "sign" - ToolVersion = "1.0" - } - ) - } - } - ) -} - -$Output = [System.IO.Path]::GetTempFileName() -$ScriptPath = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent -& "$ScriptPath\ESRPClient\packages\Microsoft.ESRPClient.*\tools\ESRPClient.exe" Sign -a $Auth -p $Policy -i $Input -o $Output diff --git a/build/gulpfile.vscode.win32.js b/build/gulpfile.vscode.win32.js index 2027dc350cf..0113607a4a9 100644 --- a/build/gulpfile.vscode.win32.js +++ b/build/gulpfile.vscode.win32.js @@ -26,7 +26,7 @@ const zipPath = arch => path.join(zipDir(arch), `VSCode-win32-${arch}.zip`); const setupDir = (arch, target) => path.join(repoPath, '.build', `win32-${arch}`, `${target}-setup`); const issPath = path.join(__dirname, 'win32', 'code.iss'); const innoSetupPath = path.join(path.dirname(path.dirname(require.resolve('innosetup'))), 'bin', 'ISCC.exe'); -const signPS1 = path.join(repoPath, 'build', 'azure-pipelines', 'win32', 'sign.ps1'); +const signWin32Path = path.join(repoPath, 'build', 'azure-pipelines', 'common', 'sign-win32'); function packageInnoSetup(iss, options, cb) { options = options || {}; @@ -49,7 +49,7 @@ function packageInnoSetup(iss, options, cb) { const args = [ iss, ...defs, - `/sesrp=powershell.exe -ExecutionPolicy bypass ${signPS1} $f` + `/sesrp=node ${signWin32Path} $f` ]; cp.spawn(innoSetupPath, args, { stdio: ['ignore', 'inherit', 'inherit'] }) diff --git a/build/package.json b/build/package.json index 43094626463..7fb5f09c70a 100644 --- a/build/package.json +++ b/build/package.json @@ -8,6 +8,7 @@ "@types/ansi-colors": "^3.2.0", "@types/azure": "0.9.19", "@types/byline": "^4.2.32", + "@types/cssnano": "^4.0.0", "@types/debounce": "^1.0.0", "@types/eslint": "4.16.1", "@types/fancy-log": "^1.3.0", @@ -17,6 +18,7 @@ "@types/gulp-filter": "^3.0.32", "@types/gulp-gzip": "^0.0.31", "@types/gulp-json-editor": "^2.2.31", + "@types/gulp-postcss": "^8.0.0", "@types/gulp-rename": "^0.0.33", "@types/gulp-sourcemaps": "^0.0.32", "@types/mime": "0.0.29", @@ -32,7 +34,9 @@ "@types/rimraf": "^2.0.4", "@types/through": "^0.0.29", "@types/through2": "^2.0.34", + "@types/tmp": "^0.2.1", "@types/underscore": "^1.8.9", + "@types/webpack": "^4.41.25", "@types/xml2js": "0.0.33", "@typescript-eslint/experimental-utils": "~2.13.0", "@typescript-eslint/parser": "^3.3.0", @@ -52,6 +56,7 @@ "p-limit": "^3.1.0", "plist": "^3.0.1", "source-map": "0.6.1", + "tmp": "^0.2.1", "typescript": "^4.4.0-dev.20210708", "vsce": "1.48.0", "vscode-universal": "deepak1556/universal#61454d96223b774c53cda10f72c2098c0ce02d58" diff --git a/build/yarn.lock b/build/yarn.lock index 8ca22bcadc3..3a23c6c2a58 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -186,6 +186,13 @@ "@types/events" "*" "@types/node" "*" +"@types/cssnano@^4.0.0": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@types/cssnano/-/cssnano-4.0.1.tgz#67fa912753d80973a016e7684a47fedf338aacff" + integrity sha512-hGOroxRTBkYl5gSBRJOffhV4+io+Y2bFX1VP7LgKEVHJt/LPPJaWUIuDAz74Vlp7l7hCDZfaDi7iPxwNwuVA4Q== + dependencies: + postcss "5 - 7" + "@types/debounce@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/debounce/-/debounce-1.0.0.tgz#417560200331e1bb84d72da85391102c2fcd61b7" @@ -286,6 +293,14 @@ "@types/js-beautify" "*" "@types/node" "*" +"@types/gulp-postcss@^8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@types/gulp-postcss/-/gulp-postcss-8.0.0.tgz#f7e86d45e4999fd43e6d8c55b00504c88a67ad61" + integrity sha512-AVgjA03bpkYONKZpzuJviB9PzaNbDzrovYPbenj8/XxivUc35C/dIzJanyaQv7CFqfLLPLsqSalmtP3GLq6iag== + dependencies: + "@types/node" "*" + "@types/vinyl" "*" + "@types/gulp-rename@^0.0.33": version "0.0.33" resolved "https://registry.yarnpkg.com/@types/gulp-rename/-/gulp-rename-0.0.33.tgz#38d146e97786569f74f5391a1b1f9b5198674b6c" @@ -428,6 +443,16 @@ "@types/glob" "*" "@types/node" "*" +"@types/source-list-map@*": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9" + integrity sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA== + +"@types/tapable@^1": + version "1.0.8" + resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.8.tgz#b94a4391c85666c7b73299fd3ad79d4faa435310" + integrity sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ== + "@types/through2@^2.0.34": version "2.0.34" resolved "https://registry.yarnpkg.com/@types/through2/-/through2-2.0.34.tgz#9c2a259a238dace2a05a2f8e94b786961bc27ac4" @@ -442,6 +467,11 @@ dependencies: "@types/node" "*" +"@types/tmp@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.2.1.tgz#83ecf4ec22a8c218c71db25f316619fe5b986011" + integrity sha512-7cTXwKP/HLOPVgjg+YhBdQ7bMiobGMuoBmrGmqwIWJv8elC6t1DfVc/mn4fD9UE1IjhwmhaQ5pGVXkmXbH0rhg== + "@types/tough-cookie@*": version "2.3.2" resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-2.3.2.tgz#e0d481d8bb282ad8a8c9e100ceb72c995fb5e709" @@ -454,6 +484,13 @@ dependencies: "@types/node" "*" +"@types/uglify-js@*": + version "3.13.1" + resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.13.1.tgz#5e889e9e81e94245c75b6450600e1c5ea2878aea" + integrity sha512-O3MmRAk6ZuAKa9CHgg0Pr0+lUOqoMLpc9AS4R8ano2auvsg7IE8syF3Xh/NPr26TWklxYcqoEEFdzLLs1fV9PQ== + dependencies: + source-map "^0.6.1" + "@types/underscore@^1.8.9": version "1.8.9" resolved "https://registry.yarnpkg.com/@types/underscore/-/underscore-1.8.9.tgz#fef41f800cd23db1b4f262ddefe49cd952d82323" @@ -489,6 +526,27 @@ dependencies: "@types/node" "*" +"@types/webpack-sources@*": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-2.1.1.tgz#6af17e3a3ded71eec2b98008d7c12f498a0a4506" + integrity sha512-MjM1R6iuw8XaVbtkCBz0N349cyqBjJHCbQiOeppe3VBeFvxqs74RKHAVt9LkxTnUWc7YLZOEsUfPUnmK6SBPKQ== + dependencies: + "@types/node" "*" + "@types/source-list-map" "*" + source-map "^0.7.3" + +"@types/webpack@^4.41.25": + version "4.41.30" + resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.30.tgz#fd3db6d0d41e145a8eeeafcd3c4a7ccde9068ddc" + integrity sha512-GUHyY+pfuQ6haAfzu4S14F+R5iGRwN6b2FRNJY7U0NilmFAqbsOfK6j1HwuLBAqwRIT+pVdNDJGJ6e8rpp0KHA== + dependencies: + "@types/node" "*" + "@types/tapable" "^1" + "@types/uglify-js" "*" + "@types/webpack-sources" "*" + anymatch "^3.0.0" + source-map "^0.6.0" + "@types/xml2js@0.0.33": version "0.0.33" resolved "https://registry.yarnpkg.com/@types/xml2js/-/xml2js-0.0.33.tgz#20c5dd6460245284d64a55690015b95e409fb7de" @@ -574,6 +632,21 @@ ajv@^6.12.3: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +anymatch@^3.0.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" + integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + applicationinsights@1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.8.tgz#db6e3d983cf9f9405fe1ee5ba30ac6e1914537b5" @@ -747,6 +820,15 @@ caseless@~0.12.0: resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= +chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + cheerio@^1.0.0-rc.1: version "1.0.0-rc.2" resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.2.tgz#4b9f53a81b27e4d5dac31c0ffd0cfa03cc6830db" @@ -771,6 +853,18 @@ clone-response@^1.0.2: dependencies: mimic-response "^1.0.0" +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + colors@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" @@ -997,6 +1091,11 @@ esbuild@^0.12.6: resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.12.6.tgz#85bc755c7cf3005d4f34b4f10f98049ce0ee67ce" integrity sha512-RDvVLvAjsq/kIZJoneMiUOH7EE7t2QaW7T3Q7EdQij14+bZbDq5sndb0tTanmHIFSqZVMBMMyqzVHkS3dJobeA== +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= + eslint-scope@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.0.0.tgz#e87c8887c73e8d1ec84f1ca591645c358bfc8fb9" @@ -1130,6 +1229,18 @@ glob@^7.0.6: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^7.1.3: + version "7.1.7" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" + integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== + 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.6: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" @@ -1182,6 +1293,11 @@ har-validator@~5.1.3: ajv "^6.12.3" har-schema "^2.0.0" +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= + hash-base@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" @@ -1487,6 +1603,11 @@ node-fetch@^2.6.0: resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + normalize-url@^4.1.0: version "4.5.1" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" @@ -1575,6 +1696,11 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= +picomatch@^2.0.4: + version "2.3.0" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" + integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== + plist@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/plist/-/plist-3.0.1.tgz#a9b931d17c304e8912ef0ba3bdd6182baf2e1f8c" @@ -1584,6 +1710,15 @@ plist@^3.0.1: xmlbuilder "^9.0.7" xmldom "0.1.x" +"postcss@5 - 7": + version "7.0.36" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.36.tgz#056f8cffa939662a8f5905950c07d5285644dfcb" + integrity sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw== + dependencies: + chalk "^2.4.2" + source-map "^0.6.1" + supports-color "^6.1.0" + priorityqueuejs@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/priorityqueuejs/-/priorityqueuejs-1.0.0.tgz#2ee4f23c2560913e08c07ce5ccdd6de3df2c5af8" @@ -1707,6 +1842,13 @@ responselike@^2.0.0: dependencies: lowercase-keys "^2.0.0" +rimraf@^3.0.0: + 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" + safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" @@ -1766,11 +1908,16 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -source-map@0.6.1: +source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +source-map@^0.7.3: + version "0.7.3" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" + integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -1803,6 +1950,20 @@ string_decoder@~0.10.x: resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" + integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== + dependencies: + has-flag "^3.0.0" + tmp@0.0.29: version "0.0.29" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.29.tgz#f25125ff0dd9da3ccb0c2dd371ee1288bb9128c0" @@ -1810,6 +1971,13 @@ tmp@0.0.29: dependencies: os-tmpdir "~1.0.1" +tmp@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" + integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== + dependencies: + rimraf "^3.0.0" + tough-cookie@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4" From 00ae2948c750ce3569182b6d2e31653ee152e20d Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 15 Jul 2020 10:08:41 +0200 Subject: [PATCH 10/66] rename some "base-xy" to "abstract-xy" --- .../browser/parts/editor/editorActions.ts | 86 +++++++++---------- src/vs/workbench/common/editor.ts | 4 +- src/vs/workbench/common/editor/editorInput.ts | 4 +- 3 files changed, 47 insertions(+), 47 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index 8c9a23b2132..d331360b573 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -43,7 +43,7 @@ export class ExecuteCommandAction extends Action { } } -export class BaseSplitEditorAction extends Action { +abstract class AbstractSplitEditorAction extends Action { private readonly toDispose = this._register(new DisposableStore()); private direction: GroupDirection; @@ -77,7 +77,7 @@ export class BaseSplitEditorAction extends Action { } } -export class SplitEditorAction extends BaseSplitEditorAction { +export class SplitEditorAction extends AbstractSplitEditorAction { static readonly ID = 'workbench.action.splitEditor'; static readonly LABEL = localize('splitEditor', "Split Editor"); @@ -92,7 +92,7 @@ export class SplitEditorAction extends BaseSplitEditorAction { } } -export class SplitEditorOrthogonalAction extends BaseSplitEditorAction { +export class SplitEditorOrthogonalAction extends AbstractSplitEditorAction { static readonly ID = 'workbench.action.splitEditorOrthogonal'; static readonly LABEL = localize('splitEditorOrthogonal', "Split Editor Orthogonal"); @@ -259,7 +259,7 @@ export class FocusActiveGroupAction extends Action { } } -export abstract class BaseFocusGroupAction extends Action { +abstract class AbstractFocusGroupAction extends Action { constructor( id: string, @@ -278,7 +278,7 @@ export abstract class BaseFocusGroupAction extends Action { } } -export class FocusFirstGroupAction extends BaseFocusGroupAction { +export class FocusFirstGroupAction extends AbstractFocusGroupAction { static readonly ID = 'workbench.action.focusFirstEditorGroup'; static readonly LABEL = localize('focusFirstEditorGroup', "Focus First Editor Group"); @@ -292,7 +292,7 @@ export class FocusFirstGroupAction extends BaseFocusGroupAction { } } -export class FocusLastGroupAction extends BaseFocusGroupAction { +export class FocusLastGroupAction extends AbstractFocusGroupAction { static readonly ID = 'workbench.action.focusLastEditorGroup'; static readonly LABEL = localize('focusLastEditorGroup', "Focus Last Editor Group"); @@ -306,7 +306,7 @@ export class FocusLastGroupAction extends BaseFocusGroupAction { } } -export class FocusNextGroup extends BaseFocusGroupAction { +export class FocusNextGroup extends AbstractFocusGroupAction { static readonly ID = 'workbench.action.focusNextGroup'; static readonly LABEL = localize('focusNextGroup', "Focus Next Editor Group"); @@ -320,7 +320,7 @@ export class FocusNextGroup extends BaseFocusGroupAction { } } -export class FocusPreviousGroup extends BaseFocusGroupAction { +export class FocusPreviousGroup extends AbstractFocusGroupAction { static readonly ID = 'workbench.action.focusPreviousGroup'; static readonly LABEL = localize('focusPreviousGroup', "Focus Previous Editor Group"); @@ -334,7 +334,7 @@ export class FocusPreviousGroup extends BaseFocusGroupAction { } } -export class FocusLeftGroup extends BaseFocusGroupAction { +export class FocusLeftGroup extends AbstractFocusGroupAction { static readonly ID = 'workbench.action.focusLeftGroup'; static readonly LABEL = localize('focusLeftGroup', "Focus Left Editor Group"); @@ -348,7 +348,7 @@ export class FocusLeftGroup extends BaseFocusGroupAction { } } -export class FocusRightGroup extends BaseFocusGroupAction { +export class FocusRightGroup extends AbstractFocusGroupAction { static readonly ID = 'workbench.action.focusRightGroup'; static readonly LABEL = localize('focusRightGroup', "Focus Right Editor Group"); @@ -362,7 +362,7 @@ export class FocusRightGroup extends BaseFocusGroupAction { } } -export class FocusAboveGroup extends BaseFocusGroupAction { +export class FocusAboveGroup extends AbstractFocusGroupAction { static readonly ID = 'workbench.action.focusAboveGroup'; static readonly LABEL = localize('focusAboveGroup', "Focus Above Editor Group"); @@ -376,7 +376,7 @@ export class FocusAboveGroup extends BaseFocusGroupAction { } } -export class FocusBelowGroup extends BaseFocusGroupAction { +export class FocusBelowGroup extends AbstractFocusGroupAction { static readonly ID = 'workbench.action.focusBelowGroup'; static readonly LABEL = localize('focusBelowGroup', "Focus Below Editor Group"); @@ -534,7 +534,7 @@ export class CloseLeftEditorsInGroupAction extends Action { } } -abstract class BaseCloseAllAction extends Action { +abstract class AbstractCloseAllAction extends Action { constructor( id: string, @@ -647,7 +647,7 @@ abstract class BaseCloseAllAction extends Action { protected abstract doCloseAll(): Promise; } -export class CloseAllEditorsAction extends BaseCloseAllAction { +export class CloseAllEditorsAction extends AbstractCloseAllAction { static readonly ID = 'workbench.action.closeAllEditors'; static readonly LABEL = localize('closeAllEditors', "Close All Editors"); @@ -673,7 +673,7 @@ export class CloseAllEditorsAction extends BaseCloseAllAction { } } -export class CloseAllEditorGroupsAction extends BaseCloseAllAction { +export class CloseAllEditorGroupsAction extends AbstractCloseAllAction { static readonly ID = 'workbench.action.closeAllGroups'; static readonly LABEL = localize('closeAllGroups', "Close All Editor Groups"); @@ -750,7 +750,7 @@ export class CloseEditorInAllGroupsAction extends Action { } } -class BaseMoveCopyGroupAction extends Action { +abstract class AbstractMoveCopyGroupAction extends Action { constructor( id: string, @@ -815,7 +815,7 @@ class BaseMoveCopyGroupAction extends Action { } } -class BaseMoveGroupAction extends BaseMoveCopyGroupAction { +abstract class AbstractMoveGroupAction extends AbstractMoveCopyGroupAction { constructor( id: string, @@ -827,7 +827,7 @@ class BaseMoveGroupAction extends BaseMoveCopyGroupAction { } } -export class MoveGroupLeftAction extends BaseMoveGroupAction { +export class MoveGroupLeftAction extends AbstractMoveGroupAction { static readonly ID = 'workbench.action.moveActiveEditorGroupLeft'; static readonly LABEL = localize('moveActiveGroupLeft', "Move Editor Group Left"); @@ -841,7 +841,7 @@ export class MoveGroupLeftAction extends BaseMoveGroupAction { } } -export class MoveGroupRightAction extends BaseMoveGroupAction { +export class MoveGroupRightAction extends AbstractMoveGroupAction { static readonly ID = 'workbench.action.moveActiveEditorGroupRight'; static readonly LABEL = localize('moveActiveGroupRight', "Move Editor Group Right"); @@ -855,7 +855,7 @@ export class MoveGroupRightAction extends BaseMoveGroupAction { } } -export class MoveGroupUpAction extends BaseMoveGroupAction { +export class MoveGroupUpAction extends AbstractMoveGroupAction { static readonly ID = 'workbench.action.moveActiveEditorGroupUp'; static readonly LABEL = localize('moveActiveGroupUp', "Move Editor Group Up"); @@ -869,7 +869,7 @@ export class MoveGroupUpAction extends BaseMoveGroupAction { } } -export class MoveGroupDownAction extends BaseMoveGroupAction { +export class MoveGroupDownAction extends AbstractMoveGroupAction { static readonly ID = 'workbench.action.moveActiveEditorGroupDown'; static readonly LABEL = localize('moveActiveGroupDown', "Move Editor Group Down"); @@ -883,7 +883,7 @@ export class MoveGroupDownAction extends BaseMoveGroupAction { } } -class BaseDuplicateGroupAction extends BaseMoveCopyGroupAction { +abstract class AbstractDuplicateGroupAction extends AbstractMoveCopyGroupAction { constructor( id: string, @@ -895,7 +895,7 @@ class BaseDuplicateGroupAction extends BaseMoveCopyGroupAction { } } -export class DuplicateGroupLeftAction extends BaseDuplicateGroupAction { +export class DuplicateGroupLeftAction extends AbstractDuplicateGroupAction { static readonly ID = 'workbench.action.duplicateActiveEditorGroupLeft'; static readonly LABEL = localize('duplicateActiveGroupLeft', "Duplicate Editor Group Left"); @@ -909,7 +909,7 @@ export class DuplicateGroupLeftAction extends BaseDuplicateGroupAction { } } -export class DuplicateGroupRightAction extends BaseDuplicateGroupAction { +export class DuplicateGroupRightAction extends AbstractDuplicateGroupAction { static readonly ID = 'workbench.action.duplicateActiveEditorGroupRight'; static readonly LABEL = localize('duplicateActiveGroupRight', "Duplicate Editor Group Right"); @@ -923,7 +923,7 @@ export class DuplicateGroupRightAction extends BaseDuplicateGroupAction { } } -export class DuplicateGroupUpAction extends BaseDuplicateGroupAction { +export class DuplicateGroupUpAction extends AbstractDuplicateGroupAction { static readonly ID = 'workbench.action.duplicateActiveEditorGroupUp'; static readonly LABEL = localize('duplicateActiveGroupUp', "Duplicate Editor Group Up"); @@ -937,7 +937,7 @@ export class DuplicateGroupUpAction extends BaseDuplicateGroupAction { } } -export class DuplicateGroupDownAction extends BaseDuplicateGroupAction { +export class DuplicateGroupDownAction extends AbstractDuplicateGroupAction { static readonly ID = 'workbench.action.duplicateActiveEditorGroupDown'; static readonly LABEL = localize('duplicateActiveGroupDown', "Duplicate Editor Group Down"); @@ -1016,7 +1016,7 @@ export class MaximizeGroupAction extends Action { } } -export abstract class BaseNavigateEditorAction extends Action { +abstract class AbstractNavigateEditorAction extends Action { constructor( id: string, @@ -1047,7 +1047,7 @@ export abstract class BaseNavigateEditorAction extends Action { protected abstract navigate(): IEditorIdentifier | undefined; } -export class OpenNextEditor extends BaseNavigateEditorAction { +export class OpenNextEditor extends AbstractNavigateEditorAction { static readonly ID = 'workbench.action.nextEditor'; static readonly LABEL = localize('openNextEditor', "Open Next Editor"); @@ -1082,7 +1082,7 @@ export class OpenNextEditor extends BaseNavigateEditorAction { } } -export class OpenPreviousEditor extends BaseNavigateEditorAction { +export class OpenPreviousEditor extends AbstractNavigateEditorAction { static readonly ID = 'workbench.action.previousEditor'; static readonly LABEL = localize('openPreviousEditor', "Open Previous Editor"); @@ -1117,7 +1117,7 @@ export class OpenPreviousEditor extends BaseNavigateEditorAction { } } -export class OpenNextEditorInGroup extends BaseNavigateEditorAction { +export class OpenNextEditorInGroup extends AbstractNavigateEditorAction { static readonly ID = 'workbench.action.nextEditorInGroup'; static readonly LABEL = localize('nextEditorInGroup', "Open Next Editor in Group"); @@ -1140,7 +1140,7 @@ export class OpenNextEditorInGroup extends BaseNavigateEditorAction { } } -export class OpenPreviousEditorInGroup extends BaseNavigateEditorAction { +export class OpenPreviousEditorInGroup extends AbstractNavigateEditorAction { static readonly ID = 'workbench.action.previousEditorInGroup'; static readonly LABEL = localize('openPreviousEditorInGroup', "Open Previous Editor in Group"); @@ -1163,7 +1163,7 @@ export class OpenPreviousEditorInGroup extends BaseNavigateEditorAction { } } -export class OpenFirstEditorInGroup extends BaseNavigateEditorAction { +export class OpenFirstEditorInGroup extends AbstractNavigateEditorAction { static readonly ID = 'workbench.action.firstEditorInGroup'; static readonly LABEL = localize('firstEditorInGroup', "Open First Editor in Group"); @@ -1185,7 +1185,7 @@ export class OpenFirstEditorInGroup extends BaseNavigateEditorAction { } } -export class OpenLastEditorInGroup extends BaseNavigateEditorAction { +export class OpenLastEditorInGroup extends AbstractNavigateEditorAction { static readonly ID = 'workbench.action.lastEditorInGroup'; static readonly LABEL = localize('lastEditorInGroup', "Open Last Editor in Group"); @@ -1359,7 +1359,7 @@ export class ShowAllEditorsByMostRecentlyUsedAction extends Action { } } -export class BaseQuickAccessEditorAction extends Action { +abstract class AbstractQuickAccessEditorAction extends Action { constructor( id: string, @@ -1382,7 +1382,7 @@ export class BaseQuickAccessEditorAction extends Action { } } -export class QuickAccessPreviousRecentlyUsedEditorAction extends BaseQuickAccessEditorAction { +export class QuickAccessPreviousRecentlyUsedEditorAction extends AbstractQuickAccessEditorAction { static readonly ID = 'workbench.action.quickOpenPreviousRecentlyUsedEditor'; static readonly LABEL = localize('quickOpenPreviousRecentlyUsedEditor', "Quick Open Previous Recently Used Editor"); @@ -1397,7 +1397,7 @@ export class QuickAccessPreviousRecentlyUsedEditorAction extends BaseQuickAccess } } -export class QuickAccessLeastRecentlyUsedEditorAction extends BaseQuickAccessEditorAction { +export class QuickAccessLeastRecentlyUsedEditorAction extends AbstractQuickAccessEditorAction { static readonly ID = 'workbench.action.quickOpenLeastRecentlyUsedEditor'; static readonly LABEL = localize('quickOpenLeastRecentlyUsedEditor', "Quick Open Least Recently Used Editor"); @@ -1412,7 +1412,7 @@ export class QuickAccessLeastRecentlyUsedEditorAction extends BaseQuickAccessEdi } } -export class QuickAccessPreviousRecentlyUsedEditorInGroupAction extends BaseQuickAccessEditorAction { +export class QuickAccessPreviousRecentlyUsedEditorInGroupAction extends AbstractQuickAccessEditorAction { static readonly ID = 'workbench.action.quickOpenPreviousRecentlyUsedEditorInGroup'; static readonly LABEL = localize('quickOpenPreviousRecentlyUsedEditorInGroup', "Quick Open Previous Recently Used Editor in Group"); @@ -1427,7 +1427,7 @@ export class QuickAccessPreviousRecentlyUsedEditorInGroupAction extends BaseQuic } } -export class QuickAccessLeastRecentlyUsedEditorInGroupAction extends BaseQuickAccessEditorAction { +export class QuickAccessLeastRecentlyUsedEditorInGroupAction extends AbstractQuickAccessEditorAction { static readonly ID = 'workbench.action.quickOpenLeastRecentlyUsedEditorInGroup'; static readonly LABEL = localize('quickOpenLeastRecentlyUsedEditorInGroup', "Quick Open Least Recently Used Editor in Group"); @@ -1817,7 +1817,7 @@ export class EditorLayoutTwoRowsRightAction extends ExecuteCommandAction { } } -export class BaseCreateEditorGroupAction extends Action { +abstract class AbstractCreateEditorGroupAction extends Action { constructor( id: string, @@ -1833,7 +1833,7 @@ export class BaseCreateEditorGroupAction extends Action { } } -export class NewEditorGroupLeftAction extends BaseCreateEditorGroupAction { +export class NewEditorGroupLeftAction extends AbstractCreateEditorGroupAction { static readonly ID = 'workbench.action.newGroupLeft'; static readonly LABEL = localize('newEditorLeft', "New Editor Group to the Left"); @@ -1847,7 +1847,7 @@ export class NewEditorGroupLeftAction extends BaseCreateEditorGroupAction { } } -export class NewEditorGroupRightAction extends BaseCreateEditorGroupAction { +export class NewEditorGroupRightAction extends AbstractCreateEditorGroupAction { static readonly ID = 'workbench.action.newGroupRight'; static readonly LABEL = localize('newEditorRight', "New Editor Group to the Right"); @@ -1861,7 +1861,7 @@ export class NewEditorGroupRightAction extends BaseCreateEditorGroupAction { } } -export class NewEditorGroupAboveAction extends BaseCreateEditorGroupAction { +export class NewEditorGroupAboveAction extends AbstractCreateEditorGroupAction { static readonly ID = 'workbench.action.newGroupAbove'; static readonly LABEL = localize('newEditorAbove', "New Editor Group Above"); @@ -1875,7 +1875,7 @@ export class NewEditorGroupAboveAction extends BaseCreateEditorGroupAction { } } -export class NewEditorGroupBelowAction extends BaseCreateEditorGroupAction { +export class NewEditorGroupBelowAction extends AbstractCreateEditorGroupAction { static readonly ID = 'workbench.action.newGroupBelow'; static readonly LABEL = localize('newEditorBelow', "New Editor Group Below"); diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index ed122cf379a..15f5e47dca8 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -618,12 +618,12 @@ export interface IEditorInput extends IDisposable { isDisposed(): boolean; } -export abstract class BaseEditorInput extends Disposable { +export abstract class AbstractEditorInput extends Disposable { // Marker class for implementing `isEditorInput` } export function isEditorInput(editor: unknown): editor is IEditorInput { - return editor instanceof BaseEditorInput; + return editor instanceof AbstractEditorInput; } export interface IEditorInputWithPreferredResource { diff --git a/src/vs/workbench/common/editor/editorInput.ts b/src/vs/workbench/common/editor/editorInput.ts index 544bbdd7f43..709fcba253d 100644 --- a/src/vs/workbench/common/editor/editorInput.ts +++ b/src/vs/workbench/common/editor/editorInput.ts @@ -7,14 +7,14 @@ import { Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { IEditorModel } from 'vs/platform/editor/common/editor'; import { firstOrDefault } from 'vs/base/common/arrays'; -import { IEditorInput, EditorInputCapabilities, Verbosity, GroupIdentifier, ISaveOptions, IRevertOptions, IMoveResult, IEditorDescriptor, IEditorPane, IUntypedEditorInput, EditorResourceAccessor, BaseEditorInput, isEditorInput } from 'vs/workbench/common/editor'; +import { IEditorInput, EditorInputCapabilities, Verbosity, GroupIdentifier, ISaveOptions, IRevertOptions, IMoveResult, IEditorDescriptor, IEditorPane, IUntypedEditorInput, EditorResourceAccessor, AbstractEditorInput, isEditorInput } from 'vs/workbench/common/editor'; import { isEqual } from 'vs/base/common/resources'; /** * Editor inputs are lightweight objects that can be passed to the workbench API to open inside the editor part. * Each editor input is mapped to an editor that is capable of opening it through the Platform facade. */ -export abstract class EditorInput extends BaseEditorInput implements IEditorInput { +export abstract class EditorInput extends AbstractEditorInput implements IEditorInput { protected readonly _onDidChangeDirty = this._register(new Emitter()); readonly onDidChangeDirty = this._onDidChangeDirty.event; From 4fb7b80bf03297de64a61111acd75805e309b401 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 9 Jul 2021 15:57:20 +0200 Subject: [PATCH 11/66] Add a setting or always restore the last editor state for editors opening in new groups (fix #102485) --- .../browser/parts/editor/editorPane.ts | 98 +++++++++++++------ .../browser/parts/editor/textEditor.ts | 2 +- .../browser/workbench.contribution.ts | 7 +- .../notebook/browser/notebookEditor.ts | 4 +- .../notebook/browser/notebookEditorWidget.ts | 5 +- .../preferences/browser/settingsEditor2.ts | 4 +- .../walkThrough/browser/walkThroughPart.ts | 6 +- .../browser/parts/editor/editorPane.test.ts | 65 ++++++++++-- 8 files changed, 148 insertions(+), 43 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorPane.ts b/src/vs/workbench/browser/parts/editor/editorPane.ts index b07b6a1c1f7..6c5171e11fa 100644 --- a/src/vs/workbench/browser/parts/editor/editorPane.ts +++ b/src/vs/workbench/browser/parts/editor/editorPane.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Composite } from 'vs/workbench/browser/composite'; -import { IEditorPane, GroupIdentifier, IEditorMemento, IEditorOpenContext } from 'vs/workbench/common/editor'; +import { IEditorPane, GroupIdentifier, IEditorMemento, IEditorOpenContext, isEditorInput } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -22,6 +22,7 @@ import { indexOfPath } from 'vs/base/common/extpath'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IEditorOptions } from 'vs/platform/editor/common/editor'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; /** * The base class of editors in the workbench. Editors register themselves for specific editor inputs. @@ -153,12 +154,12 @@ export abstract class EditorPane extends Composite implements IEditorPane { this._group = group; } - protected getEditorMemento(editorGroupService: IEditorGroupsService, key: string, limit: number = 10): IEditorMemento { + protected getEditorMemento(editorGroupService: IEditorGroupsService, configurationService: ITextResourceConfigurationService, key: string, limit: number = 10): IEditorMemento { const mementoKey = `${this.getId()}${key}`; let editorMemento = EditorPane.EDITOR_MEMENTOS.get(mementoKey); if (!editorMemento) { - editorMemento = new EditorMemento(this.getId(), key, this.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE), limit, editorGroupService); + editorMemento = new EditorMemento(this.getId(), key, this.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE), limit, editorGroupService, configurationService); EditorPane.EDITOR_MEMENTOS.set(mementoKey, editorMemento); } @@ -186,21 +187,37 @@ export abstract class EditorPane extends Composite implements IEditorPane { } interface MapGroupToMemento { - [group: number]: T; + [group: GroupIdentifier]: T; } export class EditorMemento implements IEditorMemento { + + private static readonly SHARED_EDITOR_STATE = -1; // pick a number < 0 to be outside group id range + private cache: LRUCache> | undefined; private cleanedUp = false; private editorDisposables: Map | undefined; + private shareEditorState = false; constructor( readonly id: string, private key: string, private memento: MementoObject, private limit: number, - private editorGroupService: IEditorGroupsService - ) { } + private editorGroupService: IEditorGroupsService, + private configurationService: ITextResourceConfigurationService + ) { + this.updateConfiguration(); + this.registerListeners(); + } + + private registerListeners(): void { + this.configurationService.onDidChangeConfiguration(() => this.updateConfiguration()); + } + + private updateConfiguration(): void { + this.shareEditorState = this.configurationService.getValue(undefined, 'workbench.editor.sharedViewState') === true; + } saveEditorState(group: IEditorGroup, resource: URI, state: T): void; saveEditorState(group: IEditorGroup, editor: EditorInput, state: T): void; @@ -212,16 +229,23 @@ export class EditorMemento implements IEditorMemento { const cache = this.doLoad(); - let mementoForResource = cache.get(resource.toString()); - if (!mementoForResource) { - mementoForResource = Object.create(null) as MapGroupToMemento; - cache.set(resource.toString(), mementoForResource); + // Ensure mementos for resource map + let mementosForResource = cache.get(resource.toString()); + if (!mementosForResource) { + mementosForResource = Object.create(null) as MapGroupToMemento; + cache.set(resource.toString(), mementosForResource); } - mementoForResource[group.id] = state; + // Store state for group + mementosForResource[group.id] = state; + + // Store state as most recent one based on settings + if (this.shareEditorState) { + mementosForResource[EditorMemento.SHARED_EDITOR_STATE] = state; + } // Automatically clear when editor input gets disposed if any - if (resourceOrEditor instanceof EditorInput) { + if (isEditorInput(resourceOrEditor)) { this.clearEditorStateOnDispose(resource, resourceOrEditor); } } @@ -236,18 +260,28 @@ export class EditorMemento implements IEditorMemento { const cache = this.doLoad(); - const mementoForResource = cache.get(resource.toString()); - if (mementoForResource) { - return mementoForResource[group.id]; + const mementosForResource = cache.get(resource.toString()); + if (mementosForResource) { + let mementoForResourceAndGroup = mementosForResource[group.id]; + + // Return state for group if present + if (mementoForResourceAndGroup) { + return mementoForResourceAndGroup; + } + + // Return most recent state based on settings otherwise + if (this.shareEditorState) { + return mementosForResource[EditorMemento.SHARED_EDITOR_STATE]; + } } - return; + return undefined; } clearEditorState(resource: URI, group?: IEditorGroup): void; clearEditorState(editor: EditorInput, group?: IEditorGroup): void; clearEditorState(resourceOrEditor: URI | EditorInput, group?: IEditorGroup): void { - if (resourceOrEditor instanceof EditorInput) { + if (isEditorInput(resourceOrEditor)) { this.editorDisposables?.delete(resourceOrEditor); } @@ -255,16 +289,20 @@ export class EditorMemento implements IEditorMemento { if (resource) { const cache = this.doLoad(); + // Clear state for group if (group) { - const resourceViewState = cache.get(resource.toString()); - if (resourceViewState) { - delete resourceViewState[group.id]; + const mementosForResource = cache.get(resource.toString()); + if (mementosForResource) { + delete mementosForResource[group.id]; - if (isEmptyObject(resourceViewState)) { + if (isEmptyObject(mementosForResource)) { cache.delete(resource.toString()); } } - } else { + } + + // Clear state across all groups for resource + else { cache.delete(resource.toString()); } } @@ -305,7 +343,7 @@ export class EditorMemento implements IEditorMemento { targetResource = joinPath(target, resource.path.substr(index + source.path.length + 1)); // parent folder got moved } - // Don't modify LRU state. + // Don't modify LRU state const value = cache.get(cacheKey, Touch.None); if (value) { cache.delete(cacheKey); @@ -315,7 +353,7 @@ export class EditorMemento implements IEditorMemento { } private doGetResource(resourceOrEditor: URI | EditorInput): URI | undefined { - if (resourceOrEditor instanceof EditorInput) { + if (isEditorInput(resourceOrEditor)) { return resourceOrEditor.resource; } @@ -354,12 +392,16 @@ export class EditorMemento implements IEditorMemento { // Remove groups from states that no longer exist. Since we modify the // cache and its is a LRU cache make a copy to ensure iteration succeeds const entries = [...cache.entries()]; - for (const [resource, mapGroupToMemento] of entries) { - for (const group of Object.keys(mapGroupToMemento)) { + for (const [resource, mapGroupToMementos] of entries) { + for (const group of Object.keys(mapGroupToMementos)) { const groupId: GroupIdentifier = Number(group); + if (groupId === EditorMemento.SHARED_EDITOR_STATE && this.shareEditorState) { + continue; // skip over shared entries if sharing is enabled + } + if (!this.editorGroupService.getGroup(groupId)) { - delete mapGroupToMemento[groupId]; - if (isEmptyObject(mapGroupToMemento)) { + delete mapGroupToMementos[groupId]; + if (isEmptyObject(mapGroupToMementos)) { cache.delete(resource); } } diff --git a/src/vs/workbench/browser/parts/editor/textEditor.ts b/src/vs/workbench/browser/parts/editor/textEditor.ts index 76c80ca50b1..502b64ca5b7 100644 --- a/src/vs/workbench/browser/parts/editor/textEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textEditor.ts @@ -68,7 +68,7 @@ export abstract class BaseTextEditor extends EditorPane implements ITextEditorPa ) { super(id, telemetryService, themeService, storageService); - this.editorMemento = this.getEditorMemento(editorGroupService, BaseTextEditor.TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY, 100); + this.editorMemento = this.getEditorMemento(editorGroupService, textResourceConfigurationService, BaseTextEditor.TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY, 100); this._register(this.textResourceConfigurationService.onDidChangeConfiguration(() => { const resource = this.getActiveResource(); diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 3a017b9f9b6..ec9c016b296 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -194,10 +194,15 @@ const registry = Registry.as(ConfigurationExtensions.Con }, 'workbench.editor.restoreViewState': { 'type': 'boolean', - 'description': localize('restoreViewState', "Restores the last view state (e.g. scroll position) when re-opening textual editors after they have been closed."), + 'description': localize('restoreViewState', "Restores the last view state (e.g. scroll position) when re-opening editors after they have been closed."), 'default': true, 'scope': ConfigurationScope.LANGUAGE_OVERRIDABLE }, + 'workbench.editor.sharedViewState': { + 'type': 'boolean', + 'description': localize('sharedViewState', "Preserves the most recent view state (e.g. scroll position) across all editor groups and restores that if no specific view state is found for the group."), + 'default': false + }, 'workbench.editor.centeredLayoutAutoResize': { 'type': 'boolean', 'default': true, diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index 2917a78b201..ef5a4fa441c 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -33,6 +33,7 @@ import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { IAction } from 'vs/base/common/actions'; import { SELECT_KERNEL_ID } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; import { NotebooKernelActionViewItem } from 'vs/workbench/contrib/notebook/browser/notebookKernelActionViewItem'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; const NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'NotebookEditorViewState'; @@ -67,9 +68,10 @@ export class NotebookEditor extends EditorPane { @INotebookEditorService private readonly _notebookWidgetService: INotebookEditorService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IFileService private readonly fileService: IFileService, + @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService ) { super(NotebookEditor.ID, telemetryService, themeService, storageService); - this._editorMemento = this.getEditorMemento(_editorGroupService, NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY); + this._editorMemento = this.getEditorMemento(_editorGroupService, configurationService, NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY); this._register(this.fileService.onDidChangeFileSystemProviderCapabilities(e => this.onDidChangeFileSystemProvider(e.scheme))); this._register(this.fileService.onDidChangeFileSystemProviderRegistrations(e => this.onDidChangeFileSystemProvider(e.scheme))); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index dd2275b89c1..b3845ce87a3 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -73,6 +73,7 @@ import { ViewContext } from 'vs/workbench/contrib/notebook/browser/viewModel/vie import { NotebookEditorToolbar } from 'vs/workbench/contrib/notebook/browser/notebookEditorToolbar'; import { INotebookRendererMessagingService } from 'vs/workbench/contrib/notebook/common/notebookRendererMessagingService'; import { IAckOutputHeight, IMarkupCellInitialization } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; const $ = DOM.$; @@ -527,12 +528,12 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor //#region Editor Core - protected getEditorMemento(editorGroupService: IEditorGroupsService, key: string, limit: number = 10): IEditorMemento { + protected getEditorMemento(editorGroupService: IEditorGroupsService, configurationService: ITextResourceConfigurationService, key: string, limit: number = 10): IEditorMemento { const mementoKey = `${NOTEBOOK_EDITOR_ID}${key}`; let editorMemento = NotebookEditorWidget.EDITOR_MEMENTOS.get(mementoKey); if (!editorMemento) { - editorMemento = new EditorMemento(NOTEBOOK_EDITOR_ID, key, this.getMemento(StorageScope.WORKSPACE), limit, editorGroupService); + editorMemento = new EditorMemento(NOTEBOOK_EDITOR_ID, key, this.getMemento(StorageScope.WORKSPACE), limit, editorGroupService, configurationService); NotebookEditorWidget.EDITOR_MEMENTOS.set(mementoKey, editorMemento); } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index 77caeea2dbb..460f5c75aa5 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -53,6 +53,7 @@ import { IUserDataSyncWorkbenchService } from 'vs/workbench/services/userDataSyn import { preferencesClearInputIcon } from 'vs/workbench/contrib/preferences/browser/preferencesIcons'; import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; import { IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; export const enum SettingsFocusContext { Search, @@ -185,6 +186,7 @@ export class SettingsEditor2 extends EditorPane { constructor( @ITelemetryService telemetryService: ITelemetryService, @IWorkbenchConfigurationService private readonly configurationService: IWorkbenchConfigurationService, + @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, @IThemeService themeService: IThemeService, @IPreferencesService private readonly preferencesService: IPreferencesService, @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -215,7 +217,7 @@ export class SettingsEditor2 extends EditorPane { this.scheduledRefreshes = new Map(); - this.editorMemento = this.getEditorMemento(editorGroupService, SETTINGS_EDITOR_STATE_KEY); + this.editorMemento = this.getEditorMemento(editorGroupService, textResourceConfigurationService, SETTINGS_EDITOR_STATE_KEY); this._register(configurationService.onDidChangeConfiguration(e => { if (e.source !== ConfigurationTarget.DEFAULT) { diff --git a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts index 58982ff59cb..7b0a63bda5f 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts @@ -15,7 +15,7 @@ import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { WalkThroughInput } from 'vs/workbench/contrib/welcome/walkThrough/browser/walkThroughInput'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { IModelService } from 'vs/editor/common/services/modelService'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -70,7 +70,7 @@ export class WalkThroughPart extends EditorPane { constructor( @ITelemetryService telemetryService: ITelemetryService, @IThemeService themeService: IThemeService, - @IModelService modelService: IModelService, + @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IOpenerService private readonly openerService: IOpenerService, @IKeybindingService private readonly keybindingService: IKeybindingService, @@ -83,7 +83,7 @@ export class WalkThroughPart extends EditorPane { ) { super(WalkThroughPart.ID, telemetryService, themeService, storageService); this.editorFocus = WALK_THROUGH_FOCUS.bindTo(this.contextKeyService); - this.editorMemento = this.getEditorMemento(editorGroupService, WALK_THROUGH_EDITOR_VIEW_STATE_PREFERENCE_KEY); + this.editorMemento = this.getEditorMemento(editorGroupService, textResourceConfigurationService, WALK_THROUGH_EDITOR_VIEW_STATE_PREFERENCE_KEY); } createEditor(container: HTMLElement): void { diff --git a/src/vs/workbench/test/browser/parts/editor/editorPane.test.ts b/src/vs/workbench/test/browser/parts/editor/editorPane.test.ts index d1fc039fad0..36a8094f37e 100644 --- a/src/vs/workbench/test/browser/parts/editor/editorPane.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editorPane.test.ts @@ -12,7 +12,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; -import { workbenchInstantiationService, TestEditorGroupView, TestEditorGroupsService, registerTestResourceEditor, TestEditorInput, createEditorPart } from 'vs/workbench/test/browser/workbenchTestServices'; +import { workbenchInstantiationService, TestEditorGroupView, TestEditorGroupsService, registerTestResourceEditor, TestEditorInput, createEditorPart, TestTextResourceConfigurationService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { URI } from 'vs/base/common/uri'; @@ -28,6 +28,7 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { TestWorkspaceTrustManagementService } from 'vs/workbench/services/workspaces/test/common/testWorkspaceTrustService'; import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; +import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; const NullThemeService = new TestThemeService(); @@ -213,6 +214,8 @@ suite('EditorPane', () => { const testGroup1 = new TestEditorGroupView(1); const testGroup4 = new TestEditorGroupView(4); + const configurationService = new TestTextResourceConfigurationService(); + const editorGroupService = new TestEditorGroupsService([ testGroup0, testGroup1, @@ -224,7 +227,7 @@ suite('EditorPane', () => { } const rawMemento = Object.create(null); - let memento = new EditorMemento('id', 'key', rawMemento, 3, editorGroupService); + let memento = new EditorMemento('id', 'key', rawMemento, 3, editorGroupService, configurationService); let res = memento.loadEditorState(testGroup0, URI.file('/A')); assert.ok(!res); @@ -259,7 +262,7 @@ suite('EditorPane', () => { memento.saveState(); - memento = new EditorMemento('id', 'key', rawMemento, 3, editorGroupService); + memento = new EditorMemento('id', 'key', rawMemento, 3, editorGroupService, configurationService); assert.ok(memento.loadEditorState(testGroup0, URI.file('/C'))); assert.ok(memento.loadEditorState(testGroup0, URI.file('/D'))); assert.ok(memento.loadEditorState(testGroup0, URI.file('/E'))); @@ -279,12 +282,13 @@ suite('EditorPane', () => { test('EditorMemento - move', function () { const testGroup0 = new TestEditorGroupView(0); + const configurationService = new TestTextResourceConfigurationService(); const editorGroupService = new TestEditorGroupsService([testGroup0]); interface TestViewState { line: number; } const rawMemento = Object.create(null); - const memento = new EditorMemento('id', 'key', rawMemento, 3, editorGroupService); + const memento = new EditorMemento('id', 'key', rawMemento, 3, editorGroupService, configurationService); memento.saveEditorState(testGroup0, URI.file('/some/folder/file-1.txt'), { line: 1 }); memento.saveEditorState(testGroup0, URI.file('/some/folder/file-2.txt'), { line: 2 }); @@ -327,7 +331,7 @@ suite('EditorPane', () => { } const rawMemento = Object.create(null); - const memento = new EditorMemento('id', 'key', rawMemento, 3, new TestEditorGroupsService()); + const memento = new EditorMemento('id', 'key', rawMemento, 3, new TestEditorGroupsService(), new TestTextResourceConfigurationService()); const testInputA = new TestEditorInput(URI.file('/A')); @@ -365,7 +369,7 @@ suite('EditorPane', () => { } const rawMemento = Object.create(null); - const memento = new EditorMemento('id', 'key', rawMemento, 3, new TestEditorGroupsService()); + const memento = new EditorMemento('id', 'key', rawMemento, 3, new TestEditorGroupsService(), new TestTextResourceConfigurationService()); const testInputA = new TestEditorInput(URI.file('/A')); @@ -401,6 +405,55 @@ suite('EditorPane', () => { assert.ok(!res); }); + test('EditorMemento - workbench.editor.sharedViewState', function () { + const testGroup0 = new TestEditorGroupView(0); + const testGroup1 = new TestEditorGroupView(1); + + const configurationService = new TestTextResourceConfigurationService(new TestConfigurationService({ + workbench: { + editor: { + sharedViewState: true + } + } + })); + const editorGroupService = new TestEditorGroupsService([testGroup0]); + + interface TestViewState { line: number; } + + const rawMemento = Object.create(null); + const memento = new EditorMemento('id', 'key', rawMemento, 3, editorGroupService, configurationService); + + const resource = URI.file('/some/folder/file-1.txt'); + memento.saveEditorState(testGroup0, resource, { line: 1 }); + + let res = memento.loadEditorState(testGroup0, resource); + assert.strictEqual(res!.line, 1); + + res = memento.loadEditorState(testGroup1, resource); + assert.strictEqual(res!.line, 1); + + memento.saveEditorState(testGroup0, resource, { line: 3 }); + + res = memento.loadEditorState(testGroup1, resource); + assert.strictEqual(res!.line, 3); + + memento.saveEditorState(testGroup1, resource, { line: 1 }); + + res = memento.loadEditorState(testGroup1, resource); + assert.strictEqual(res!.line, 1); + + memento.clearEditorState(resource, testGroup0); + memento.clearEditorState(resource, testGroup1); + + res = memento.loadEditorState(testGroup1, resource); + assert.strictEqual(res!.line, 1); + + memento.clearEditorState(resource); + + res = memento.loadEditorState(testGroup1, resource); + assert.ok(!res); + }); + test('WorkspaceTrustRequiredEditor', async function () { class TrustRequiredTestEditor extends EditorPane { From 4189c23b969a33ed34a0ddcd92bea1f4c16609b6 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 9 Jul 2021 17:07:20 +0200 Subject: [PATCH 12/66] Fix #128121 --- .../abstractExtensionManagementService.ts | 646 ++++++++++++++ .../common/extensionManagement.ts | 4 +- .../common/extensionManagementIpc.ts | 12 +- .../node/extensionManagementService.ts | 788 +++--------------- .../extensionRecommendationsService.ts | 4 +- .../browser/extensionsWorkbenchService.ts | 10 +- .../common/webExtensionManagementService.ts | 185 ++-- .../common/webExtensionsScannerService.ts | 84 +- 8 files changed, 921 insertions(+), 812 deletions(-) create mode 100644 src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts diff --git a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts new file mode 100644 index 00000000000..083e7c2cf0b --- /dev/null +++ b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts @@ -0,0 +1,646 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { toDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { + IExtensionManagementService, IExtensionGalleryService, ILocalExtension, + IGalleryExtension, + InstallExtensionEvent, DidUninstallExtensionEvent, + IExtensionIdentifier, + IReportedExtension, + InstallOperation, + INSTALL_ERROR_MALICIOUS, + INSTALL_ERROR_INCOMPATIBLE, + ExtensionManagementError, + InstallOptions, + InstallVSIXOptions, + InstallExtensionResult, + UninstallOptions, + IGalleryMetadata, + StatisticType +} from 'vs/platform/extensionManagement/common/extensionManagement'; +import { areSameExtensions, getMaliciousExtensionsSet, getGalleryExtensionTelemetryData, ExtensionIdentifierWithVersion, getLocalExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { Event, Emitter } from 'vs/base/common/event'; +import product from 'vs/platform/product/common/product'; +import { ILogService } from 'vs/platform/log/common/log'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { ExtensionType, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; +import { canceled, getErrorMessage } from 'vs/base/common/errors'; +import { URI } from 'vs/base/common/uri'; +import { Barrier, CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; + +export const INSTALL_ERROR_VALIDATING = 'validating'; +export const ERROR_UNKNOWN = 'unknown'; +export const INSTALL_ERROR_LOCAL = 'local'; + +export interface IInstallExtensionTask { + readonly identifier: IExtensionIdentifier; + readonly source: IGalleryExtension | URI; + readonly operation: InstallOperation; + run(): Promise; + waitUntilTaskIsFinished(): Promise; + cancel(): void; +} + +export type UninstallExtensionTaskOptions = { readonly remove?: boolean; readonly versionOnly?: boolean }; + +export interface IUninstallExtensionTask { + readonly extension: ILocalExtension; + run(): Promise; + waitUntilTaskIsFinished(): Promise; + cancel(): void; +} + +export abstract class AbstractExtensionManagementService extends Disposable implements IExtensionManagementService { + + declare readonly _serviceBrand: undefined; + + private reportedExtensions: Promise | undefined; + private lastReportTimestamp = 0; + private readonly installingExtensions = new Map(); + private readonly uninstallingExtensions = new Map(); + + private readonly _onInstallExtension = this._register(new Emitter()); + readonly onInstallExtension: Event = this._onInstallExtension.event; + + protected readonly _onDidInstallExtensions = this._register(new Emitter()); + readonly onDidInstallExtensions = this._onDidInstallExtensions.event; + + protected readonly _onUninstallExtension = this._register(new Emitter()); + readonly onUninstallExtension: Event = this._onUninstallExtension.event; + + protected _onDidUninstallExtension = this._register(new Emitter()); + onDidUninstallExtension: Event = this._onDidUninstallExtension.event; + + constructor( + @IExtensionGalleryService protected readonly galleryService: IExtensionGalleryService, + @ITelemetryService protected readonly telemetryService: ITelemetryService, + @ILogService protected readonly logService: ILogService, + ) { + super(); + this._register(toDisposable(() => { + this.installingExtensions.forEach(task => task.cancel()); + this.uninstallingExtensions.forEach(promise => promise.cancel()); + this.installingExtensions.clear(); + this.uninstallingExtensions.clear(); + })); + } + + async installFromGallery(extension: IGalleryExtension, options: InstallOptions = {}): Promise { + if (!this.galleryService.isEnabled()) { + throw new Error(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled")); + } + + try { + extension = await this.checkAndGetCompatibleVersion(extension); + } catch (error) { + this.logService.error(getErrorMessage(error)); + reportTelemetry(this.telemetryService, 'extensionGallery:install', getGalleryExtensionTelemetryData(extension), undefined, error); + throw error; + } + + if (!await this.canInstall(extension)) { + const error = new ExtensionManagementError(`Not supported`, INSTALL_ERROR_VALIDATING); + this.logService.error(`Canno install extension as it is not supported.`, extension.identifier.id, error.message); + reportTelemetry(this.telemetryService, 'extensionGallery:install', getGalleryExtensionTelemetryData(extension), undefined, error); + throw error; + } + + const manifest = await this.galleryService.getManifest(extension, CancellationToken.None); + if (manifest === null) { + const error = new ExtensionManagementError(`Missing manifest for extension ${extension.identifier.id}`, INSTALL_ERROR_VALIDATING); + this.logService.error(`Failed to install extension:`, extension.identifier.id, error.message); + reportTelemetry(this.telemetryService, 'extensionGallery:install', getGalleryExtensionTelemetryData(extension), undefined, error); + throw error; + } + + return this.installExtension(manifest, extension, options); + } + + async uninstall(extension: ILocalExtension, options: UninstallOptions = {}): Promise { + this.logService.trace('ExtensionManagementService#uninstall', extension.identifier.id); + return this.unininstallExtension(extension, options); + } + + async reinstallFromGallery(extension: ILocalExtension): Promise { + this.logService.trace('ExtensionManagementService#reinstallFromGallery', extension.identifier.id); + if (!this.galleryService.isEnabled()) { + throw new Error(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled")); + } + + const galleryExtension = await this.findGalleryExtension(extension); + if (!galleryExtension) { + throw new Error(nls.localize('Not a Marketplace extension', "Only Marketplace Extensions can be reinstalled")); + } + + await this.createUninstallExtensionTask(extension, { remove: true, versionOnly: true }).run(); + await this.installFromGallery(galleryExtension); + } + + getExtensionsReport(): Promise { + const now = new Date().getTime(); + + if (!this.reportedExtensions || now - this.lastReportTimestamp > 1000 * 60 * 5) { // 5 minute cache freshness + this.reportedExtensions = this.updateReportCache(); + this.lastReportTimestamp = now; + } + + return this.reportedExtensions; + } + + protected async installExtension(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: InstallOptions & InstallVSIXOptions): Promise { + // only cache gallery extensions tasks + if (!URI.isUri(extension)) { + let installExtensionTask = this.installingExtensions.get(new ExtensionIdentifierWithVersion(extension.identifier, extension.version).key()); + if (installExtensionTask) { + this.logService.info('Extensions is already requested to install', extension.identifier.id); + return installExtensionTask.waitUntilTaskIsFinished(); + } + options = { ...options, installOnlyNewlyAddedFromExtensionPack: true /* always true for gallery extensions */ }; + } + + const allInstallExtensionTasks: { task: IInstallExtensionTask, manifest: IExtensionManifest }[] = []; + const installResults: (InstallExtensionResult & { local: ILocalExtension })[] = []; + const installExtensionTask = this.createInstallExtensionTask(manifest, extension, options); + if (!URI.isUri(extension)) { + this.installingExtensions.set(new ExtensionIdentifierWithVersion(installExtensionTask.identifier, manifest.version).key(), installExtensionTask); + } + this._onInstallExtension.fire({ identifier: installExtensionTask.identifier, source: extension }); + this.logService.info('Installing extension:', installExtensionTask.identifier.id); + allInstallExtensionTasks.push({ task: installExtensionTask, manifest }); + let installExtensionHasDependents: boolean = false; + + try { + if (options.donotIncludePackAndDependencies) { + this.logService.info('Installing the extension without checking dependencies and pack', installExtensionTask.identifier.id); + } else { + try { + const allDepsAndPackExtensionsToInstall = await this.getAllDepsAndPackExtensionsToInstall(installExtensionTask.identifier, manifest, !!options.installOnlyNewlyAddedFromExtensionPack); + for (const { gallery, manifest } of allDepsAndPackExtensionsToInstall) { + installExtensionHasDependents = installExtensionHasDependents || !!manifest.extensionDependencies?.some(id => areSameExtensions({ id }, installExtensionTask.identifier)); + if (this.installingExtensions.has(new ExtensionIdentifierWithVersion(gallery.identifier, gallery.version).key())) { + this.logService.info('Extension is already requested to install', gallery.identifier.id); + } else { + const task = this.createInstallExtensionTask(manifest, gallery, { ...options, donotIncludePackAndDependencies: true }); + this.installingExtensions.set(new ExtensionIdentifierWithVersion(task.identifier, manifest.version).key(), task); + this._onInstallExtension.fire({ identifier: task.identifier, source: gallery }); + this.logService.info('Installing extension:', task.identifier.id); + allInstallExtensionTasks.push({ task, manifest }); + } + } + } catch (error) { + this.logService.error('Error while preparing to install dependencies and extension packs of the extension:', installExtensionTask.identifier.id); + this.logService.error(error); + throw error; + } + } + + const extensionsToInstallMap = allInstallExtensionTasks.reduce((result, { task, manifest }) => { + result.set(task.identifier.id.toLowerCase(), { task, manifest }); + return result; + }, new Map()); + + while (extensionsToInstallMap.size) { + let extensionsToInstall; + const extensionsWithoutDepsToInstall = [...extensionsToInstallMap.values()].filter(({ manifest }) => !manifest.extensionDependencies?.some(id => extensionsToInstallMap.has(id.toLowerCase()))); + if (extensionsWithoutDepsToInstall.length) { + extensionsToInstall = extensionsToInstallMap.size === 1 ? extensionsWithoutDepsToInstall + /* If the main extension has no dependents remove it and install it at the end */ + : extensionsWithoutDepsToInstall.filter(({ task }) => !(task === installExtensionTask && !installExtensionHasDependents)); + } else { + this.logService.info('Found extensions with circular dependencies', extensionsWithoutDepsToInstall.map(({ task }) => task.identifier.id)); + extensionsToInstall = [...extensionsToInstallMap.values()]; + } + + // Install extensions in parallel and wait until all extensions are installed / failed + const result = await Promise.allSettled(extensionsToInstall.map(async ({ task }) => { + const startTime = new Date().getTime(); + try { + const local = await task.run(); + if (!URI.isUri(task.source)) { + reportTelemetry(this.telemetryService, task.operation === InstallOperation.Update ? 'extensionGallery:update' : 'extensionGallery:install', getGalleryExtensionTelemetryData(task.source), new Date().getTime() - startTime, undefined); + } + installResults.push({ local, identifier: task.identifier, operation: task.operation, source: task.source }); + } catch (error) { + if (!URI.isUri(task.source)) { + reportTelemetry(this.telemetryService, task.operation === InstallOperation.Update ? 'extensionGallery:update' : 'extensionGallery:install', getGalleryExtensionTelemetryData(task.source), new Date().getTime() - startTime, error); + } + this.logService.error('Error while installing the extension:', task.identifier.id); + this.logService.error(error); + throw error; + } finally { extensionsToInstallMap.delete(task.identifier.id.toLowerCase()); } + })); + + // Collect the errors + const errors = result.reduce((errors, r) => { if (r.status === 'rejected') { errors.push(r.reason); } return errors; }, []); + // If there are errors, throw the error. + if (errors.length) { throw joinErrors(errors); } + } + + installResults.forEach(({ identifier }) => this.logService.info(`Extension installed successfully:`, identifier.id)); + this._onDidInstallExtensions.fire(installResults); + return installResults.filter(({ identifier }) => areSameExtensions(identifier, installExtensionTask.identifier))[0].local; + + } catch (error) { + + // cancel all tasks + allInstallExtensionTasks.forEach(({ task }) => task.cancel()); + + // rollback installed extensions + if (installResults.length) { + try { + const result = await Promise.allSettled(installResults.map(({ local }) => this.createUninstallExtensionTask(local, { versionOnly: true }).run())); + for (let index = 0; index < result.length; index++) { + const r = result[index]; + const { identifier } = installResults[index]; + if (r.status === 'fulfilled') { + this.logService.info('Rollback: Uninstalled extension', identifier.id); + } else { + this.logService.warn('Rollback: Error while uninstalling extension', identifier.id, getErrorMessage(r.reason)); + } + } + } catch (error) { + // ignore error + this.logService.warn('Error while rolling back extensions', getErrorMessage(error), installResults.map(({ identifier }) => identifier.id)); + } + } + + this.logService.error(`Failed to install extension:`, installExtensionTask.identifier.id, getErrorMessage(error)); + this._onDidInstallExtensions.fire(allInstallExtensionTasks.map(({ task }) => ({ identifier: task.identifier, operation: InstallOperation.Install, source: task.source }))); + + if (error instanceof Error) { + error.name = error && (error).code ? (error).code : ERROR_UNKNOWN; + } + throw error; + } finally { + /* Remove the gallery tasks from the cache */ + for (const { task, manifest } of allInstallExtensionTasks) { + if (!URI.isUri(task.source)) { + const key = new ExtensionIdentifierWithVersion(task.identifier, manifest.version).key(); + if (!this.installingExtensions.delete(key)) { + this.logService.warn('Installation task is not found in the cache', key); + } + } + } + } + } + + private async getAllDepsAndPackExtensionsToInstall(extensionIdentifier: IExtensionIdentifier, manifest: IExtensionManifest, getOnlyNewlyAddedFromExtensionPack: boolean): Promise<{ gallery: IGalleryExtension, manifest: IExtensionManifest }[]> { + if (!this.galleryService.isEnabled()) { + return []; + } + + let installed = await this.getInstalled(); + const knownIdentifiers = [extensionIdentifier, ...(installed).map(i => i.identifier)]; + + const allDependenciesAndPacks: { gallery: IGalleryExtension, manifest: IExtensionManifest }[] = []; + const collectDependenciesAndPackExtensionsToInstall = async (extensionIdentifier: IExtensionIdentifier, manifest: IExtensionManifest): Promise => { + const dependenciesAndPackExtensions: string[] = manifest.extensionDependencies || []; + if (manifest.extensionPack) { + const existing = getOnlyNewlyAddedFromExtensionPack ? installed.find(e => areSameExtensions(e.identifier, extensionIdentifier)) : undefined; + for (const extension of manifest.extensionPack) { + // add only those extensions which are new in currently installed extension + if (!(existing && existing.manifest.extensionPack && existing.manifest.extensionPack.some(old => areSameExtensions({ id: old }, { id: extension })))) { + if (dependenciesAndPackExtensions.every(e => !areSameExtensions({ id: e }, { id: extension }))) { + dependenciesAndPackExtensions.push(extension); + } + } + } + } + + if (dependenciesAndPackExtensions.length) { + // filter out installed and known extensions + const identifiers = [...knownIdentifiers, ...allDependenciesAndPacks.map(r => r.gallery.identifier)]; + const names = dependenciesAndPackExtensions.filter(id => identifiers.every(galleryIdentifier => !areSameExtensions(galleryIdentifier, { id }))); + if (names.length) { + const galleryResult = await this.galleryService.query({ names, pageSize: dependenciesAndPackExtensions.length }, CancellationToken.None); + for (const galleryExtension of galleryResult.firstPage) { + if (identifiers.find(identifier => areSameExtensions(identifier, galleryExtension.identifier))) { + continue; + } + const compatibleExtension = await this.checkAndGetCompatibleVersion(galleryExtension); + if (!await this.canInstall(compatibleExtension)) { + this.logService.info('Skipping the extension as it cannot be installed', compatibleExtension.identifier.id); + continue; + } + const manifest = await this.galleryService.getManifest(compatibleExtension, CancellationToken.None); + if (manifest === null) { + throw new ExtensionManagementError(`Missing manifest for extension ${compatibleExtension.identifier.id}`, INSTALL_ERROR_VALIDATING); + } + allDependenciesAndPacks.push({ gallery: compatibleExtension, manifest }); + await collectDependenciesAndPackExtensionsToInstall(compatibleExtension.identifier, manifest); + } + } + } + }; + + await collectDependenciesAndPackExtensionsToInstall(extensionIdentifier, manifest); + installed = await this.getInstalled(); + return allDependenciesAndPacks.filter(e => !installed.some(i => areSameExtensions(i.identifier, e.gallery.identifier))); + } + + private async checkAndGetCompatibleVersion(extension: IGalleryExtension): Promise { + if (await this.isMalicious(extension)) { + throw new ExtensionManagementError(nls.localize('malicious extension', "Can't install '{0}' extension since it was reported to be problematic.", extension.identifier.id), INSTALL_ERROR_MALICIOUS); + } + + const compatibleExtension = await this.galleryService.getCompatibleExtension(extension); + if (!compatibleExtension) { + throw new ExtensionManagementError(nls.localize('notFoundCompatibleDependency', "Can't install '{0}' extension because it is not compatible with the current version of VS Code (version {1}).", extension.identifier.id, product.version), INSTALL_ERROR_INCOMPATIBLE); + } + + return compatibleExtension; + } + + private async isMalicious(extension: IGalleryExtension): Promise { + const report = await this.getExtensionsReport(); + return getMaliciousExtensionsSet(report).has(extension.identifier.id); + } + + private async unininstallExtension(extension: ILocalExtension, options: UninstallOptions): Promise { + const uninstallExtensionTask = this.uninstallingExtensions.get(extension.identifier.id.toLowerCase()); + if (uninstallExtensionTask) { + this.logService.info('Extensions is already requested to uninstall', extension.identifier.id); + return uninstallExtensionTask.waitUntilTaskIsFinished(); + } + + const createUninstallExtensionTask = (extension: ILocalExtension, options: UninstallExtensionTaskOptions): IUninstallExtensionTask => { + const uninstallExtensionTask = this.createUninstallExtensionTask(extension, options); + this.uninstallingExtensions.set(uninstallExtensionTask.extension.identifier.id.toLowerCase(), uninstallExtensionTask); + this.logService.info('Uninstalling extension:', extension.identifier.id); + this._onUninstallExtension.fire(extension.identifier); + return uninstallExtensionTask; + }; + + const postUninstallExtension = (extension: ILocalExtension, error?: ExtensionManagementError): void => { + if (error) { + this.logService.error('Failed to uninstall extension:', extension.identifier.id, error.message); + } else { + this.logService.info('Successfully uninstalled extension:', extension.identifier.id); + } + reportTelemetry(this.telemetryService, 'extensionGallery:uninstall', getLocalExtensionTelemetryData(extension), undefined, error); + this._onDidUninstallExtension.fire({ identifier: extension.identifier, error: error?.code }); + }; + + const allTasks: IUninstallExtensionTask[] = []; + const processedTasks: IUninstallExtensionTask[] = []; + + try { + allTasks.push(createUninstallExtensionTask(extension, {})); + const installed = await this.getInstalled(ExtensionType.User); + + if (options.donotIncludePack) { + this.logService.info('Uninstalling the extension without including packed extension', extension.identifier.id); + } else { + const packedExtensions = this.getAllPackExtensionsToUninstall(extension, installed); + for (const packedExtension of packedExtensions) { + if (this.uninstallingExtensions.has(packedExtension.identifier.id.toLowerCase())) { + this.logService.info('Extensions is already requested to uninstall', packedExtension.identifier.id); + } else { + allTasks.push(createUninstallExtensionTask(packedExtension, {})); + } + } + } + + if (options.donotCheckDependents) { + this.logService.info('Uninstalling the extension without checking dependents', extension.identifier.id); + } else { + this.checkForDependents(allTasks.map(task => task.extension), installed, extension); + } + + // Uninstall extensions in parallel and wait until all extensions are uninstalled / failed + const result = await Promise.allSettled(allTasks.map(async task => { + try { + await task.run(); + // only report if extension has a mapped gallery extension. UUID identifies the gallery extension. + if (task.extension.identifier.uuid) { + try { + await this.galleryService.reportStatistic(task.extension.manifest.publisher, task.extension.manifest.name, task.extension.manifest.version, StatisticType.Uninstall); + } catch (error) { /* ignore */ } + } + postUninstallExtension(task.extension); + } catch (e) { + const error = e instanceof ExtensionManagementError ? e : new ExtensionManagementError(getErrorMessage(e), ERROR_UNKNOWN); + postUninstallExtension(task.extension, error); + throw error; + } finally { + processedTasks.push(task); + } + })); + + // Collect the errors + const errors = result.reduce((errors, r) => { if (r.status === 'rejected') { errors.push(r.reason); } return errors; }, []); + // If there are errors, throw the error. + if (errors.length) { throw joinErrors(errors); } + + } catch (e) { + const error = e instanceof ExtensionManagementError ? e : new ExtensionManagementError(getErrorMessage(e), ERROR_UNKNOWN); + for (const task of allTasks) { + // cancel the tasks + try { task.cancel(); } catch (error) { /* ignore */ } + if (!processedTasks.includes(task)) { + postUninstallExtension(task.extension, error); + } + } + throw error; + } finally { + // Remove tasks from cache + for (const task of allTasks) { + if (!this.uninstallingExtensions.delete(task.extension.identifier.id.toLowerCase())) { + this.logService.warn('Uninstallation task is not found in the cache', task.extension.identifier.id); + } + } + } + } + + private checkForDependents(extensionsToUninstall: ILocalExtension[], installed: ILocalExtension[], extensionToUninstall: ILocalExtension): void { + for (const extension of extensionsToUninstall) { + const dependents = this.getDependents(extension, installed); + if (dependents.length) { + const remainingDependents = dependents.filter(dependent => extensionsToUninstall.indexOf(dependent) === -1); + if (remainingDependents.length) { + throw new Error(this.getDependentsErrorMessage(extension, remainingDependents, extensionToUninstall)); + } + } + } + } + + private getDependentsErrorMessage(dependingExtension: ILocalExtension, dependents: ILocalExtension[], extensionToUninstall: ILocalExtension): string { + if (extensionToUninstall === dependingExtension) { + if (dependents.length === 1) { + return nls.localize('singleDependentError', "Cannot uninstall '{0}' extension. '{1}' extension depends on this.", + extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name); + } + if (dependents.length === 2) { + return nls.localize('twoDependentsError', "Cannot uninstall '{0}' extension. '{1}' and '{2}' extensions depend on this.", + extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name, dependents[1].manifest.displayName || dependents[1].manifest.name); + } + return nls.localize('multipleDependentsError', "Cannot uninstall '{0}' extension. '{1}', '{2}' and other extension depend on this.", + extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name, dependents[1].manifest.displayName || dependents[1].manifest.name); + } + if (dependents.length === 1) { + return nls.localize('singleIndirectDependentError', "Cannot uninstall '{0}' extension . It includes uninstalling '{1}' extension and '{2}' extension depends on this.", + extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependingExtension.manifest.displayName + || dependingExtension.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name); + } + if (dependents.length === 2) { + return nls.localize('twoIndirectDependentsError', "Cannot uninstall '{0}' extension. It includes uninstalling '{1}' extension and '{2}' and '{3}' extensions depend on this.", + extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependingExtension.manifest.displayName + || dependingExtension.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name, dependents[1].manifest.displayName || dependents[1].manifest.name); + } + return nls.localize('multipleIndirectDependentsError', "Cannot uninstall '{0}' extension. It includes uninstalling '{1}' extension and '{2}', '{3}' and other extensions depend on this.", + extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependingExtension.manifest.displayName + || dependingExtension.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name, dependents[1].manifest.displayName || dependents[1].manifest.name); + + } + + private getAllPackExtensionsToUninstall(extension: ILocalExtension, installed: ILocalExtension[], checked: ILocalExtension[] = []): ILocalExtension[] { + if (checked.indexOf(extension) !== -1) { + return []; + } + checked.push(extension); + const extensionsPack = extension.manifest.extensionPack ? extension.manifest.extensionPack : []; + if (extensionsPack.length) { + const packedExtensions = installed.filter(i => !i.isBuiltin && extensionsPack.some(id => areSameExtensions({ id }, i.identifier))); + const packOfPackedExtensions: ILocalExtension[] = []; + for (const packedExtension of packedExtensions) { + packOfPackedExtensions.push(...this.getAllPackExtensionsToUninstall(packedExtension, installed, checked)); + } + return [...packedExtensions, ...packOfPackedExtensions]; + } + return []; + } + + private getDependents(extension: ILocalExtension, installed: ILocalExtension[]): ILocalExtension[] { + return installed.filter(e => e.manifest.extensionDependencies && e.manifest.extensionDependencies.some(id => areSameExtensions({ id }, extension.identifier))); + } + + private async findGalleryExtension(local: ILocalExtension): Promise { + if (local.identifier.uuid) { + const galleryExtension = await this.findGalleryExtensionById(local.identifier.uuid); + return galleryExtension ? galleryExtension : this.findGalleryExtensionByName(local.identifier.id); + } + return this.findGalleryExtensionByName(local.identifier.id); + } + + private async findGalleryExtensionById(uuid: string): Promise { + const galleryResult = await this.galleryService.query({ ids: [uuid], pageSize: 1 }, CancellationToken.None); + return galleryResult.firstPage[0]; + } + + private async findGalleryExtensionByName(name: string): Promise { + const galleryResult = await this.galleryService.query({ names: [name], pageSize: 1 }, CancellationToken.None); + return galleryResult.firstPage[0]; + } + + private async updateReportCache(): Promise { + try { + this.logService.trace('ExtensionManagementService.refreshReportedCache'); + const result = await this.galleryService.getExtensionsReport(); + this.logService.trace(`ExtensionManagementService.refreshReportedCache - got ${result.length} reported extensions from service`); + return result; + } catch (err) { + this.logService.trace('ExtensionManagementService.refreshReportedCache - failed to get extension report'); + return []; + } + } + + abstract zip(extension: ILocalExtension): Promise; + abstract unzip(zipLocation: URI): Promise; + abstract getManifest(vsix: URI): Promise; + abstract install(vsix: URI, options?: InstallVSIXOptions): Promise; + abstract canInstall(extension: IGalleryExtension): Promise; + abstract getInstalled(type?: ExtensionType): Promise; + + abstract updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise; + abstract updateExtensionScope(local: ILocalExtension, isMachineScoped: boolean): Promise; + + protected abstract createInstallExtensionTask(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: InstallOptions & InstallVSIXOptions): IInstallExtensionTask; + protected abstract createUninstallExtensionTask(extension: ILocalExtension, options: UninstallExtensionTaskOptions): IUninstallExtensionTask; +} + +export function joinErrors(errorOrErrors: (Error | string) | (Array)): Error { + const errors = Array.isArray(errorOrErrors) ? errorOrErrors : [errorOrErrors]; + if (errors.length === 1) { + return errors[0] instanceof Error ? errors[0] : new Error(errors[0]); + } + return errors.reduce((previousValue: Error, currentValue: Error | string) => { + return new Error(`${previousValue.message}${previousValue.message ? ',' : ''}${currentValue instanceof Error ? currentValue.message : currentValue}`); + }, new Error('')); +} + +export function reportTelemetry(telemetryService: ITelemetryService, eventName: string, extensionData: any, duration?: number, error?: Error): void { + const errorcode = error ? error instanceof ExtensionManagementError ? error.code : ERROR_UNKNOWN : undefined; + /* __GDPR__ + "extensionGallery:install" : { + "success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, + "duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, + "errorcode": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" }, + "recommendationReason": { "retiredFromVersion": "1.23.0", "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "${include}": [ + "${GalleryExtensionTelemetryData}" + ] + } + */ + /* __GDPR__ + "extensionGallery:uninstall" : { + "success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, + "duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, + "errorcode": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" }, + "${include}": [ + "${GalleryExtensionTelemetryData}" + ] + } + */ + /* __GDPR__ + "extensionGallery:update" : { + "success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, + "duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, + "errorcode": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" }, + "${include}": [ + "${GalleryExtensionTelemetryData}" + ] + } + */ + telemetryService.publicLogError(eventName, { ...extensionData, success: !error, duration, errorcode }); +} + +export abstract class AbstractExtensionTask { + + private readonly barrier = new Barrier(); + private cancellablePromise: CancelablePromise | undefined; + + async waitUntilTaskIsFinished(): Promise { + await this.barrier.wait(); + return this.cancellablePromise!; + } + + async run(): Promise { + if (!this.cancellablePromise) { + this.cancellablePromise = createCancelablePromise(token => this.doRun(token)); + } + this.barrier.open(); + return this.cancellablePromise; + } + + cancel(): void { + if (!this.cancellablePromise) { + this.cancellablePromise = createCancelablePromise(token => { + return new Promise((c, e) => { + const disposable = token.onCancellationRequested(() => { + disposable.dispose(); + e(canceled()); + }); + }); + }); + this.barrier.open(); + } + this.cancellablePromise.cancel(); + } + + protected abstract doRun(token: CancellationToken): Promise; +} diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index d7dd6e2403f..7cf697e0d45 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -174,13 +174,13 @@ export interface IExtensionGalleryService { export interface InstallExtensionEvent { identifier: IExtensionIdentifier; - source: string | IGalleryExtension; + source: URI | IGalleryExtension; } export interface InstallExtensionResult { readonly identifier: IExtensionIdentifier; readonly operation: InstallOperation; - readonly source?: string | IGalleryExtension; + readonly source?: URI | IGalleryExtension; readonly local?: ILocalExtension; } diff --git a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts index 427c86c9bd4..8c7587e22ad 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts @@ -98,12 +98,20 @@ export class ExtensionManagementChannelClient extends Disposable implements IExt private readonly channel: IChannel, ) { super(); - this._register(this.channel.listen('onInstallExtension')(e => this._onInstallExtension.fire(e))); - this._register(this.channel.listen('onDidInstallExtensions')(results => this._onDidInstallExtensions.fire(results.map(e => ({ ...e, local: e.local ? transformIncomingExtension(e.local, null) : e.local }))))); + this._register(this.channel.listen('onInstallExtension')(e => this._onInstallExtension.fire({ identifier: e.identifier, source: this.isUriComponents(e.source) ? URI.revive(e.source) : e.source }))); + this._register(this.channel.listen('onDidInstallExtensions')(results => this._onDidInstallExtensions.fire(results.map(e => ({ ...e, local: e.local ? transformIncomingExtension(e.local, null) : e.local, source: this.isUriComponents(e.source) ? URI.revive(e.source) : e.source }))))); this._register(this.channel.listen('onUninstallExtension')(e => this._onUninstallExtension.fire(e))); this._register(this.channel.listen('onDidUninstallExtension')(e => this._onDidUninstallExtension.fire(e))); } + private isUriComponents(thing: unknown): thing is UriComponents { + if (!thing) { + return false; + } + return typeof (thing).path === 'string' && + typeof (thing).scheme === 'string'; + } + zip(extension: ILocalExtension): Promise { return Promise.resolve(this.channel.call('zip', [extension]).then(result => URI.revive(result))); } diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 3edb7536719..a87d5fdafe6 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -6,28 +6,18 @@ import * as nls from 'vs/nls'; import * as path from 'vs/base/common/path'; import * as pfs from 'vs/base/node/pfs'; -import { toDisposable, Disposable } from 'vs/base/common/lifecycle'; import { zip, IFile } from 'vs/base/node/zip'; import { IExtensionManagementService, IExtensionGalleryService, ILocalExtension, IGalleryExtension, IGalleryMetadata, - InstallExtensionEvent, DidUninstallExtensionEvent, - StatisticType, IExtensionIdentifier, - IReportedExtension, InstallOperation, - INSTALL_ERROR_MALICIOUS, - INSTALL_ERROR_INCOMPATIBLE, ExtensionManagementError, InstallOptions, - UninstallOptions, InstallVSIXOptions, - InstallExtensionResult } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { areSameExtensions, getGalleryExtensionId, getMaliciousExtensionsSet, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, ExtensionIdentifierWithVersion } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { areSameExtensions, getGalleryExtensionId, ExtensionIdentifierWithVersion } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; -import { createCancelablePromise, CancelablePromise, Promises, Barrier } from 'vs/base/common/async'; -import { Event, Emitter } from 'vs/base/common/event'; import * as semver from 'vs/base/common/semver/semver'; import { URI } from 'vs/base/common/uri'; import product from 'vs/platform/product/common/product'; @@ -50,14 +40,10 @@ import { ExtensionsScanner, ILocalExtensionManifest, IMetadata } from 'vs/platfo import { ExtensionsLifecycle } from 'vs/platform/extensionManagement/node/extensionLifecycle'; import { ExtensionsWatcher } from 'vs/platform/extensionManagement/node/extensionsWatcher'; import { IFileService } from 'vs/platform/files/common/files'; -import { canceled, getErrorMessage } from 'vs/base/common/errors'; -import { isString } from 'vs/base/common/types'; +import { AbstractExtensionManagementService, joinErrors, IUninstallExtensionTask, IInstallExtensionTask, INSTALL_ERROR_VALIDATING, UninstallExtensionTaskOptions, AbstractExtensionTask } from 'vs/platform/extensionManagement/common/abstractExtensionManagementService'; const INSTALL_ERROR_UNSET_UNINSTALLED = 'unsetUninstalled'; const INSTALL_ERROR_DOWNLOADING = 'downloading'; -const INSTALL_ERROR_VALIDATING = 'validating'; -const INSTALL_ERROR_LOCAL = 'local'; -const ERROR_UNKNOWN = 'unknown'; interface InstallableExtension { zipPath: string; @@ -65,49 +51,22 @@ interface InstallableExtension { metadata?: IMetadata; } -interface InstallExtensionTask { - readonly identifier: IExtensionIdentifier; - readonly source: IGalleryExtension | string; - readonly operation: InstallOperation; - run(): Promise; - waitUntilTaskIsFinished(): Promise; - cancel(): void; -} - -export class ExtensionManagementService extends Disposable implements IExtensionManagementService { - - declare readonly _serviceBrand: undefined; +export class ExtensionManagementService extends AbstractExtensionManagementService implements IExtensionManagementService { private readonly extensionsScanner: ExtensionsScanner; - private reportedExtensions: Promise | undefined; - private lastReportTimestamp = 0; - private readonly installingExtensions = new Map(); - private readonly uninstallingExtensions: Map> = new Map>(); private readonly manifestCache: ExtensionsManifestCache; private readonly extensionsDownloader: ExtensionsDownloader; - private readonly _onInstallExtension = this._register(new Emitter()); - readonly onInstallExtension: Event = this._onInstallExtension.event; - - private readonly _onDidInstallExtensions = this._register(new Emitter()); - readonly onDidInstallExtensions = this._onDidInstallExtensions.event; - - private readonly _onUninstallExtension = this._register(new Emitter()); - readonly onUninstallExtension: Event = this._onUninstallExtension.event; - - private _onDidUninstallExtension = this._register(new Emitter()); - onDidUninstallExtension: Event = this._onDidUninstallExtension.event; - constructor( + @IExtensionGalleryService galleryService: IExtensionGalleryService, + @ITelemetryService telemetryService: ITelemetryService, + @ILogService logService: ILogService, @INativeEnvironmentService private readonly environmentService: INativeEnvironmentService, - @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, - @ILogService private readonly logService: ILogService, @optional(IDownloadService) private downloadService: IDownloadService, - @ITelemetryService private readonly telemetryService: ITelemetryService, @IInstantiationService instantiationService: IInstantiationService, @IFileService fileService: IFileService, ) { - super(); + super(galleryService, telemetryService, logService); const extensionLifecycle = this._register(instantiationService.createInstance(ExtensionsLifecycle)); this.extensionsScanner = this._register(instantiationService.createInstance(ExtensionsScanner, extension => extensionLifecycle.postUninstall(extension))); this.manifestCache = this._register(new ExtensionsManifestCache(environmentService, this)); @@ -120,13 +79,6 @@ export class ExtensionManagementService extends Disposable implements IExtension } removed.forEach(extension => this._onDidUninstallExtension.fire({ identifier: extension })); })); - - this._register(toDisposable(() => { - this.installingExtensions.forEach(task => task.cancel()); - this.uninstallingExtensions.forEach(promise => promise.cancel()); - this.installingExtensions.clear(); - this.uninstallingExtensions.clear(); - })); } async zip(extension: ILocalExtension): Promise { @@ -148,6 +100,65 @@ export class ExtensionManagementService extends Disposable implements IExtension return getManifest(zipPath); } + getInstalled(type: ExtensionType | null = null): Promise { + return this.extensionsScanner.scanExtensions(type); + } + + async canInstall(extension: IGalleryExtension): Promise { + return true; + } + + async install(vsix: URI, options: InstallVSIXOptions = {}): Promise { + this.logService.trace('ExtensionManagementService#install', vsix.toString()); + + const downloadLocation = await this.downloadVsix(vsix); + const manifest = await getManifest(path.resolve(downloadLocation.fsPath)); + if (manifest.engines && manifest.engines.vscode && !isEngineValid(manifest.engines.vscode, product.version, product.date)) { + throw new Error(nls.localize('incompatible', "Unable to install extension '{0}' as it is not compatible with VS Code '{1}'.", getGalleryExtensionId(manifest.publisher, manifest.name), product.version)); + } + + return this.installExtension(manifest, downloadLocation, options); + } + + async updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise { + this.logService.trace('ExtensionManagementService#updateMetadata', local.identifier.id); + local = await this.extensionsScanner.saveMetadataForLocalExtension(local, { ...((local.manifest).__metadata || {}), ...metadata }); + this.manifestCache.invalidate(); + return local; + } + + async updateExtensionScope(local: ILocalExtension, isMachineScoped: boolean): Promise { + this.logService.trace('ExtensionManagementService#updateExtensionScope', local.identifier.id); + local = await this.extensionsScanner.saveMetadataForLocalExtension(local, { ...((local.manifest).__metadata || {}), isMachineScoped }); + this.manifestCache.invalidate(); + return local; + } + + removeDeprecatedExtensions(): Promise { + return this.extensionsScanner.cleanUp(); + } + + private async downloadVsix(vsix: URI): Promise { + if (vsix.scheme === Schemas.file) { + return vsix; + } + if (!this.downloadService) { + throw new Error('Download service is not available'); + } + + const downloadedLocation = joinPath(this.environmentService.tmpDir, generateUuid()); + await this.downloadService.download(vsix, downloadedLocation); + return downloadedLocation; + } + + protected createInstallExtensionTask(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: InstallOptions & InstallVSIXOptions): IInstallExtensionTask { + return URI.isUri(extension) ? new InstallVSIXTask(manifest, extension, options, this.galleryService, this.extensionsScanner, this.logService) : new InstallGalleryExtensionTask(extension, options, this.extensionsDownloader, this.extensionsScanner, this.logService); + } + + protected createUninstallExtensionTask(extension: ILocalExtension, options: UninstallExtensionTaskOptions): IUninstallExtensionTask { + return new UninstallExtensionTask(extension, options, this.extensionsScanner); + } + private async collectFiles(extension: ILocalExtension): Promise { const collectFilesFromDirectory = async (dir: string): Promise => { @@ -173,588 +184,20 @@ export class ExtensionManagementService extends Disposable implements IExtension return files.map(f => ({ path: `extension/${path.relative(extension.location.fsPath, f)}`, localPath: f })); } - async canInstall(extension: IGalleryExtension): Promise { - return true; - } - - async install(vsix: URI, options: InstallVSIXOptions = {}): Promise { - this.logService.trace('ExtensionManagementService#install', vsix.toString()); - - const downloadLocation = await this.downloadVsix(vsix); - const zipPath = path.resolve(downloadLocation.fsPath); - const manifest = await getManifest(zipPath); - if (manifest.engines && manifest.engines.vscode && !isEngineValid(manifest.engines.vscode, product.version, product.date)) { - throw new Error(nls.localize('incompatible', "Unable to install extension '{0}' as it is not compatible with VS Code '{1}'.", getGalleryExtensionId(manifest.publisher, manifest.name), product.version)); - } - - return this.installExtension(manifest, zipPath, options); - } - - async installFromGallery(extension: IGalleryExtension, options: InstallOptions = {}): Promise { - if (!this.galleryService.isEnabled()) { - throw new Error(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled")); - } - - try { - extension = await this.checkAndGetCompatibleVersion(extension); - } catch (error) { - this.logService.error(getErrorMessage(error)); - reportTelemetry(this.telemetryService, 'extensionGallery:install', getGalleryExtensionTelemetryData(extension), undefined, error); - throw error; - } - - const manifest = await this.galleryService.getManifest(extension, CancellationToken.None); - if (manifest === null) { - const error = new ExtensionManagementError(`Missing manifest for extension ${extension.identifier.id}`, INSTALL_ERROR_VALIDATING); - this.logService.error(`Failed to install extension:`, extension.identifier.id, error.message); - reportTelemetry(this.telemetryService, 'extensionGallery:install', getGalleryExtensionTelemetryData(extension), undefined, error); - throw error; - } - - return this.installExtension(manifest, extension, options); - } - - private async downloadVsix(vsix: URI): Promise { - if (vsix.scheme === Schemas.file) { - return vsix; - } - if (!this.downloadService) { - throw new Error('Download service is not available'); - } - - const downloadedLocation = joinPath(this.environmentService.tmpDir, generateUuid()); - await this.downloadService.download(vsix, downloadedLocation); - return downloadedLocation; - } - - private createInstallVSIXExtensionTask(manifest: IExtensionManifest, zipPath: string, options: InstallVSIXOptions): InstallVSIXTask { - return new InstallVSIXTask(manifest, zipPath, options, this.galleryService, this.extensionsScanner, this.logService); - } - - private createInstallFromGalleryExtensionTask(extension: IGalleryExtension, options: InstallOptions): InstallExtensionTask { - return new InstallGalleryExtensionTask(extension, options, this.extensionsDownloader, this.telemetryService, this.extensionsScanner, this.logService); - } - - private createInstallExtensionTask(manifest: IExtensionManifest, extension: string | IGalleryExtension, options: InstallOptions & InstallVSIXOptions): InstallExtensionTask { - return isString(extension) ? this.createInstallVSIXExtensionTask(manifest, extension, options) : this.createInstallFromGalleryExtensionTask(extension, options); - } - - private async installExtension(manifest: IExtensionManifest, extension: string | IGalleryExtension, options: InstallOptions & InstallVSIXOptions): Promise { - // only cache gallery extensions tasks - if (!isString(extension)) { - let installExtensionTask = this.installingExtensions.get(new ExtensionIdentifierWithVersion(extension.identifier, extension.version).key()); - if (installExtensionTask) { - this.logService.info('Extensions is already requested to install', extension.identifier.id); - return installExtensionTask.waitUntilTaskIsFinished(); - } - options = { ...options, installOnlyNewlyAddedFromExtensionPack: true /* always true for gallery extensions */ }; - } - - const allInstallExtensionTasks: { task: InstallExtensionTask, manifest: IExtensionManifest }[] = []; - const installResults: (InstallExtensionResult & { local: ILocalExtension })[] = []; - const installExtensionTask = this.createInstallExtensionTask(manifest, extension, options); - if (!isString(extension)) { - this.installingExtensions.set(new ExtensionIdentifierWithVersion(installExtensionTask.identifier, manifest.version).key(), installExtensionTask); - } - this._onInstallExtension.fire({ identifier: installExtensionTask.identifier, source: extension }); - this.logService.info('Installing extension:', installExtensionTask.identifier.id); - allInstallExtensionTasks.push({ task: installExtensionTask, manifest }); - let installExtensionHasDependents: boolean = false; - - try { - if (options.donotIncludePackAndDependencies) { - this.logService.info('Installing the extension without checking dependencies and pack', installExtensionTask.identifier.id); - } else { - try { - const allDepsAndPackExtensionsToInstall = await this.getAllDepsAndPackExtensionsToInstall(installExtensionTask.identifier, manifest, !!options.installOnlyNewlyAddedFromExtensionPack); - for (const { gallery, manifest } of allDepsAndPackExtensionsToInstall) { - installExtensionHasDependents = installExtensionHasDependents || !!manifest.extensionDependencies?.some(id => areSameExtensions({ id }, installExtensionTask.identifier)); - if (this.installingExtensions.has(new ExtensionIdentifierWithVersion(gallery.identifier, gallery.version).key())) { - this.logService.info('Extension is already requested to install', gallery.identifier.id); - } else { - const task = this.createInstallExtensionTask(manifest, gallery, { ...options, donotIncludePackAndDependencies: true }); - this.installingExtensions.set(new ExtensionIdentifierWithVersion(task.identifier, manifest.version).key(), task); - this._onInstallExtension.fire({ identifier: task.identifier, source: gallery }); - this.logService.info('Installing extension:', task.identifier.id); - allInstallExtensionTasks.push({ task, manifest }); - } - } - } catch (error) { - this.logService.error('Error while preparing to install dependencies and extension packs of the extension:', installExtensionTask.identifier.id); - this.logService.error(error); - throw error; - } - } - - const extensionsToInstallMap = allInstallExtensionTasks.reduce((result, { task, manifest }) => { - result.set(task.identifier.id.toLowerCase(), { task, manifest }); - return result; - }, new Map()); - - while (extensionsToInstallMap.size) { - let extensionsToInstall; - const extensionsWithoutDepsToInstall = [...extensionsToInstallMap.values()].filter(({ manifest }) => !manifest.extensionDependencies?.some(id => extensionsToInstallMap.has(id.toLowerCase()))); - if (extensionsWithoutDepsToInstall.length) { - extensionsToInstall = extensionsToInstallMap.size === 1 ? extensionsWithoutDepsToInstall - /* If the main extension has no dependents remove it and install it at the end */ - : extensionsWithoutDepsToInstall.filter(({ task }) => !(task === installExtensionTask && !installExtensionHasDependents)); - } else { - this.logService.info('Found extensions with circular dependencies', extensionsWithoutDepsToInstall.map(({ task }) => task.identifier.id)); - extensionsToInstall = [...extensionsToInstallMap.values()]; - } - - // Install extensions in parallel and wait until all extensions are installed / failed - const result = await Promise.allSettled(extensionsToInstall.map(async ({ task }) => { - try { - const local = await task.run(); - installResults.push({ local, identifier: task.identifier, operation: task.operation, source: task.source }); - } catch (error) { - this.logService.error('Error while installing the extension:', task.identifier.id); - this.logService.error(error); - throw error; - } finally { extensionsToInstallMap.delete(task.identifier.id.toLowerCase()); } - })); - - // Collect the errors - const errors = result.reduce((errors, r) => { if (r.status === 'rejected') { errors.push(r.reason); } return errors; }, []); - // If there are errors, throw the error. - if (errors.length) { throw joinErrors(errors); } - } - - installResults.forEach(({ identifier }) => this.logService.info(`Extensions installed successfully:`, identifier.id)); - this._onDidInstallExtensions.fire(installResults); - return installResults.filter(({ identifier }) => areSameExtensions(identifier, installExtensionTask.identifier))[0].local; - - } catch (error) { - - // cancel all tasks - allInstallExtensionTasks.forEach(({ task }) => task.cancel()); - - // rollback installed extensions - if (installResults.length) { - try { - await this.extensionsScanner.setUninstalled(...installResults.map(({ local }) => local)); - this.logService.info('Rollback: Uninstalled extensions', ...installResults.map(({ identifier }) => identifier.id)); - } catch (error) { - // ignore error - this.logService.warn('Error while rolling back extensions', getErrorMessage(error)); - } - } - - this.logService.error(`Failed to install extension:`, installExtensionTask.identifier.id, getErrorMessage(error)); - this._onDidInstallExtensions.fire(allInstallExtensionTasks.map(({ task }) => ({ identifier: task.identifier, operation: InstallOperation.Install, source: task.source }))); - - if (error instanceof Error) { - error.name = error && (error).code ? (error).code : ERROR_UNKNOWN; - } - throw error; - } finally { - /* Remove the gallery tasks from the cache */ - for (const { task, manifest } of allInstallExtensionTasks) { - if (!isString(task.source)) { - const key = new ExtensionIdentifierWithVersion(task.identifier, manifest.version).key(); - if (!this.installingExtensions.delete(key)) { - this.logService.warn('Installation task is not found in the cache', key); - } - } - } - } - } - - private async getAllDepsAndPackExtensionsToInstall(extensionIdentifier: IExtensionIdentifier, manifest: IExtensionManifest, getOnlyNewlyAddedFromExtensionPack: boolean): Promise<{ gallery: IGalleryExtension, manifest: IExtensionManifest }[]> { - if (!this.galleryService.isEnabled()) { - return []; - } - - let installed = await this.getInstalled(); - const knownIdentifiers = [extensionIdentifier, ...(installed).map(i => i.identifier)]; - - const allDependenciesAndPacks: { gallery: IGalleryExtension, manifest: IExtensionManifest }[] = []; - const collectDependenciesAndPackExtensionsToInstall = async (extensionIdentifier: IExtensionIdentifier, manifest: IExtensionManifest): Promise => { - const dependenciesAndPackExtensions: string[] = manifest.extensionDependencies || []; - if (manifest.extensionPack) { - const existing = getOnlyNewlyAddedFromExtensionPack ? installed.find(e => areSameExtensions(e.identifier, extensionIdentifier)) : undefined; - for (const extension of manifest.extensionPack) { - // add only those extensions which are new in currently installed extension - if (!(existing && existing.manifest.extensionPack && existing.manifest.extensionPack.some(old => areSameExtensions({ id: old }, { id: extension })))) { - if (dependenciesAndPackExtensions.every(e => !areSameExtensions({ id: e }, { id: extension }))) { - dependenciesAndPackExtensions.push(extension); - } - } - } - } - - if (dependenciesAndPackExtensions.length) { - // filter out installed and known extensions - const identifiers = [...knownIdentifiers, ...allDependenciesAndPacks.map(r => r.gallery.identifier)]; - const names = dependenciesAndPackExtensions.filter(id => identifiers.every(galleryIdentifier => !areSameExtensions(galleryIdentifier, { id }))); - if (names.length) { - const galleryResult = await this.galleryService.query({ names, pageSize: dependenciesAndPackExtensions.length }, CancellationToken.None); - for (const galleryExtension of galleryResult.firstPage) { - if (identifiers.find(identifier => areSameExtensions(identifier, galleryExtension.identifier))) { - continue; - } - const compatibleExtension = await this.checkAndGetCompatibleVersion(galleryExtension); - if (!await this.canInstall(compatibleExtension)) { - this.logService.info('Skipping the extension as it cannot be installed', compatibleExtension.identifier.id); - continue; - } - const manifest = await this.galleryService.getManifest(compatibleExtension, CancellationToken.None); - if (manifest === null) { - throw new ExtensionManagementError(`Missing manifest for extension ${compatibleExtension.identifier.id}`, INSTALL_ERROR_VALIDATING); - } - allDependenciesAndPacks.push({ gallery: compatibleExtension, manifest }); - await collectDependenciesAndPackExtensionsToInstall(compatibleExtension.identifier, manifest); - } - } - } - }; - - await collectDependenciesAndPackExtensionsToInstall(extensionIdentifier, manifest); - installed = await this.getInstalled(); - return allDependenciesAndPacks.filter(e => !installed.some(i => areSameExtensions(i.identifier, e.gallery.identifier))); - } - - private async checkAndGetCompatibleVersion(extension: IGalleryExtension): Promise { - if (await this.isMalicious(extension)) { - throw new ExtensionManagementError(nls.localize('malicious extension', "Can't install '{0}' extension since it was reported to be problematic.", extension.identifier.id), INSTALL_ERROR_MALICIOUS); - } - - const compatibleExtension = await this.galleryService.getCompatibleExtension(extension); - if (!compatibleExtension) { - throw new ExtensionManagementError(nls.localize('notFoundCompatibleDependency', "Can't install '{0}' extension because it is not compatible with the current version of VS Code (version {1}).", extension.identifier.id, product.version), INSTALL_ERROR_INCOMPATIBLE); - } - - return compatibleExtension; - } - - async reinstallFromGallery(extension: ILocalExtension): Promise { - this.logService.trace('ExtensionManagementService#reinstallFromGallery', extension.identifier.id); - if (!this.galleryService.isEnabled()) { - throw new Error(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled")); - } - - const galleryExtension = await this.findGalleryExtension(extension); - if (!galleryExtension) { - throw new Error(nls.localize('Not a Marketplace extension', "Only Marketplace Extensions can be reinstalled")); - } - - await this.extensionsScanner.setUninstalled(extension); - try { - await this.extensionsScanner.removeUninstalledExtension(extension); - } catch (e) { - throw new Error(nls.localize('removeError', "Error while removing the extension: {0}. Please Quit and Start VS Code before trying again.", toErrorMessage(e))); - } - - await this.installFromGallery(galleryExtension); - } - - private async isMalicious(extension: IGalleryExtension): Promise { - const report = await this.getExtensionsReport(); - return getMaliciousExtensionsSet(report).has(extension.identifier.id); - } - - async uninstall(extension: ILocalExtension, options: UninstallOptions = {}): Promise { - this.logService.trace('ExtensionManagementService#uninstall', extension.identifier.id); - const installed = await this.getInstalled(ExtensionType.User); - const extensionToUninstall = installed.find(e => areSameExtensions(e.identifier, extension.identifier)); - if (!extensionToUninstall) { - throw new Error(nls.localize('notInstalled', "Extension '{0}' is not installed.", extension.manifest.displayName || extension.manifest.name)); - } - - try { - await this.checkForDependenciesAndUninstall(extensionToUninstall, installed, options); - } catch (error) { - throw joinErrors(error); - } - } - - async updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise { - this.logService.trace('ExtensionManagementService#updateMetadata', local.identifier.id); - local = await this.extensionsScanner.saveMetadataForLocalExtension(local, { ...((local.manifest).__metadata || {}), ...metadata }); - this.manifestCache.invalidate(); - return local; - } - - async updateExtensionScope(local: ILocalExtension, isMachineScoped: boolean): Promise { - this.logService.trace('ExtensionManagementService#updateExtensionScope', local.identifier.id); - local = await this.extensionsScanner.saveMetadataForLocalExtension(local, { ...((local.manifest).__metadata || {}), isMachineScoped }); - this.manifestCache.invalidate(); - return local; - } - - private async findGalleryExtension(local: ILocalExtension): Promise { - if (local.identifier.uuid) { - const galleryExtension = await this.findGalleryExtensionById(local.identifier.uuid); - return galleryExtension ? galleryExtension : this.findGalleryExtensionByName(local.identifier.id); - } - return this.findGalleryExtensionByName(local.identifier.id); - } - - private async findGalleryExtensionById(uuid: string): Promise { - const galleryResult = await this.galleryService.query({ ids: [uuid], pageSize: 1 }, CancellationToken.None); - return galleryResult.firstPage[0]; - } - - private async findGalleryExtensionByName(name: string): Promise { - const galleryResult = await this.galleryService.query({ names: [name], pageSize: 1 }, CancellationToken.None); - return galleryResult.firstPage[0]; - } - - private async checkForDependenciesAndUninstall(extension: ILocalExtension, installed: ILocalExtension[], options: UninstallOptions): Promise { - try { - await this.preUninstallExtension(extension); - const packedExtensions = options.donotIncludePack ? [] : this.getAllPackExtensionsToUninstall(extension, installed); - await this.uninstallExtensions(extension, packedExtensions, installed, options); - } catch (error) { - await this.postUninstallExtension(extension, new ExtensionManagementError(error instanceof Error ? error.message : error, INSTALL_ERROR_LOCAL)); - throw error; - } - await this.postUninstallExtension(extension); - } - - private async uninstallExtensions(extension: ILocalExtension, otherExtensionsToUninstall: ILocalExtension[], installed: ILocalExtension[], options: UninstallOptions): Promise { - const extensionsToUninstall = [extension, ...otherExtensionsToUninstall]; - if (!options.donotCheckDependents) { - for (const e of extensionsToUninstall) { - this.checkForDependents(e, extensionsToUninstall, installed, extension); - } - } - await Promises.settled([this.uninstallExtension(extension), ...otherExtensionsToUninstall.map(d => this.doUninstall(d))]); - } - - private checkForDependents(extension: ILocalExtension, extensionsToUninstall: ILocalExtension[], installed: ILocalExtension[], extensionToUninstall: ILocalExtension): void { - const dependents = this.getDependents(extension, installed); - if (dependents.length) { - const remainingDependents = dependents.filter(dependent => extensionsToUninstall.indexOf(dependent) === -1); - if (remainingDependents.length) { - throw new Error(this.getDependentsErrorMessage(extension, remainingDependents, extensionToUninstall)); - } - } - } - - private getDependentsErrorMessage(dependingExtension: ILocalExtension, dependents: ILocalExtension[], extensionToUninstall: ILocalExtension): string { - if (extensionToUninstall === dependingExtension) { - if (dependents.length === 1) { - return nls.localize('singleDependentError', "Cannot uninstall '{0}' extension. '{1}' extension depends on this.", - extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name); - } - if (dependents.length === 2) { - return nls.localize('twoDependentsError', "Cannot uninstall '{0}' extension. '{1}' and '{2}' extensions depend on this.", - extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name, dependents[1].manifest.displayName || dependents[1].manifest.name); - } - return nls.localize('multipleDependentsError', "Cannot uninstall '{0}' extension. '{1}', '{2}' and other extension depend on this.", - extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name, dependents[1].manifest.displayName || dependents[1].manifest.name); - } - if (dependents.length === 1) { - return nls.localize('singleIndirectDependentError', "Cannot uninstall '{0}' extension . It includes uninstalling '{1}' extension and '{2}' extension depends on this.", - extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependingExtension.manifest.displayName - || dependingExtension.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name); - } - if (dependents.length === 2) { - return nls.localize('twoIndirectDependentsError', "Cannot uninstall '{0}' extension. It includes uninstalling '{1}' extension and '{2}' and '{3}' extensions depend on this.", - extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependingExtension.manifest.displayName - || dependingExtension.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name, dependents[1].manifest.displayName || dependents[1].manifest.name); - } - return nls.localize('multipleIndirectDependentsError', "Cannot uninstall '{0}' extension. It includes uninstalling '{1}' extension and '{2}', '{3}' and other extensions depend on this.", - extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependingExtension.manifest.displayName - || dependingExtension.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name, dependents[1].manifest.displayName || dependents[1].manifest.name); - - } - - private getAllPackExtensionsToUninstall(extension: ILocalExtension, installed: ILocalExtension[], checked: ILocalExtension[] = []): ILocalExtension[] { - if (checked.indexOf(extension) !== -1) { - return []; - } - checked.push(extension); - const extensionsPack = extension.manifest.extensionPack ? extension.manifest.extensionPack : []; - if (extensionsPack.length) { - const packedExtensions = installed.filter(i => !i.isBuiltin && extensionsPack.some(id => areSameExtensions({ id }, i.identifier))); - const packOfPackedExtensions: ILocalExtension[] = []; - for (const packedExtension of packedExtensions) { - packOfPackedExtensions.push(...this.getAllPackExtensionsToUninstall(packedExtension, installed, checked)); - } - return [...packedExtensions, ...packOfPackedExtensions]; - } - return []; - } - - private getDependents(extension: ILocalExtension, installed: ILocalExtension[]): ILocalExtension[] { - return installed.filter(e => e.manifest.extensionDependencies && e.manifest.extensionDependencies.some(id => areSameExtensions({ id }, extension.identifier))); - } - - private async doUninstall(extension: ILocalExtension): Promise { - try { - await this.preUninstallExtension(extension); - await this.uninstallExtension(extension); - } catch (error) { - await this.postUninstallExtension(extension, new ExtensionManagementError(error instanceof Error ? error.message : error, INSTALL_ERROR_LOCAL)); - throw error; - } - await this.postUninstallExtension(extension); - } - - private async preUninstallExtension(extension: ILocalExtension): Promise { - const exists = await pfs.Promises.exists(extension.location.fsPath); - if (!exists) { - throw new Error(nls.localize('notExists', "Could not find extension")); - } - this.logService.info('Uninstalling extension:', extension.identifier.id); - this._onUninstallExtension.fire(extension.identifier); - } - - private async uninstallExtension(local: ILocalExtension): Promise { - let promise = this.uninstallingExtensions.get(local.identifier.id); - if (!promise) { - // Set all versions of the extension as uninstalled - promise = createCancelablePromise(async () => { - const userExtensions = await this.extensionsScanner.scanUserExtensions(false); - await this.extensionsScanner.setUninstalled(...userExtensions.filter(u => areSameExtensions(u.identifier, local.identifier))); - }); - this.uninstallingExtensions.set(local.identifier.id, promise); - promise.finally(() => this.uninstallingExtensions.delete(local.identifier.id)); - } - return promise; - } - - private async postUninstallExtension(extension: ILocalExtension, error?: Error): Promise { - if (error) { - this.logService.error('Failed to uninstall extension:', extension.identifier.id, error.message); - } else { - this.logService.info('Successfully uninstalled extension:', extension.identifier.id); - // only report if extension has a mapped gallery extension. UUID identifies the gallery extension. - if (extension.identifier.uuid) { - try { - await this.galleryService.reportStatistic(extension.manifest.publisher, extension.manifest.name, extension.manifest.version, StatisticType.Uninstall); - } catch (error) { /* ignore */ } - } - } - reportTelemetry(this.telemetryService, 'extensionGallery:uninstall', getLocalExtensionTelemetryData(extension), undefined, error); - const errorcode = error ? error instanceof ExtensionManagementError ? error.code : ERROR_UNKNOWN : undefined; - this._onDidUninstallExtension.fire({ identifier: extension.identifier, error: errorcode }); - } - - getInstalled(type: ExtensionType | null = null): Promise { - return this.extensionsScanner.scanExtensions(type); - } - - removeDeprecatedExtensions(): Promise { - return this.extensionsScanner.cleanUp(); - } - - getExtensionsReport(): Promise { - const now = new Date().getTime(); - - if (!this.reportedExtensions || now - this.lastReportTimestamp > 1000 * 60 * 5) { // 5 minute cache freshness - this.reportedExtensions = this.updateReportCache(); - this.lastReportTimestamp = now; - } - - return this.reportedExtensions; - } - - private async updateReportCache(): Promise { - try { - this.logService.trace('ExtensionManagementService.refreshReportedCache'); - const result = await this.galleryService.getExtensionsReport(); - this.logService.trace(`ExtensionManagementService.refreshReportedCache - got ${result.length} reported extensions from service`); - return result; - } catch (err) { - this.logService.trace('ExtensionManagementService.refreshReportedCache - failed to get extension report'); - return []; - } - } - } -function joinErrors(errorOrErrors: (Error | string) | (Array)): Error { - const errors = Array.isArray(errorOrErrors) ? errorOrErrors : [errorOrErrors]; - if (errors.length === 1) { - return errors[0] instanceof Error ? errors[0] : new Error(errors[0]); - } - return errors.reduce((previousValue: Error, currentValue: Error | string) => { - return new Error(`${previousValue.message}${previousValue.message ? ',' : ''}${currentValue instanceof Error ? currentValue.message : currentValue}`); - }, new Error('')); -} - -function reportTelemetry(telemetryService: ITelemetryService, eventName: string, extensionData: any, duration?: number, error?: Error): void { - const errorcode = error ? error instanceof ExtensionManagementError ? error.code : ERROR_UNKNOWN : undefined; - /* __GDPR__ - "extensionGallery:install" : { - "success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, - "duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, - "errorcode": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" }, - "recommendationReason": { "retiredFromVersion": "1.23.0", "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "${include}": [ - "${GalleryExtensionTelemetryData}" - ] - } - */ - /* __GDPR__ - "extensionGallery:uninstall" : { - "success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, - "duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, - "errorcode": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" }, - "${include}": [ - "${GalleryExtensionTelemetryData}" - ] - } - */ - /* __GDPR__ - "extensionGallery:update" : { - "success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, - "duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, - "errorcode": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" }, - "${include}": [ - "${GalleryExtensionTelemetryData}" - ] - } - */ - telemetryService.publicLogError(eventName, { ...extensionData, success: !error, duration, errorcode }); -} - -abstract class AbstractInstallExtensionTask implements InstallExtensionTask { - - private readonly barrier = new Barrier(); - private cancellablePromise: CancelablePromise | undefined; +abstract class AbstractInstallExtensionTask extends AbstractExtensionTask implements IInstallExtensionTask { protected _operation = InstallOperation.Install; get operation() { return this._operation; } constructor( readonly identifier: IExtensionIdentifier, - readonly source: string | IGalleryExtension, + readonly source: URI | IGalleryExtension, protected readonly extensionsScanner: ExtensionsScanner, protected readonly logService: ILogService, ) { - } - - async waitUntilTaskIsFinished(): Promise { - await this.barrier.wait(); - return this.cancellablePromise!; - } - - async run(): Promise { - if (!this.cancellablePromise) { - this.cancellablePromise = createCancelablePromise(token => this.install(token)); - } - this.barrier.open(); - return this.cancellablePromise; - } - - cancel(): void { - if (!this.cancellablePromise) { - this.cancellablePromise = createCancelablePromise(token => { - return new Promise((c, e) => { - const disposable = token.onCancellationRequested(() => { - disposable.dispose(); - e(canceled()); - }); - }); - }); - this.barrier.open(); - } - this.cancellablePromise.cancel(); + super(); } protected async installExtension(installableExtension: InstallableExtension, token: CancellationToken): Promise { @@ -801,7 +244,6 @@ abstract class AbstractInstallExtensionTask implements InstallExtensionTask { return local; } - abstract install(token: CancellationToken): Promise; } class InstallGalleryExtensionTask extends AbstractInstallExtensionTask { @@ -810,45 +252,29 @@ class InstallGalleryExtensionTask extends AbstractInstallExtensionTask { private readonly gallery: IGalleryExtension, private readonly options: InstallOptions, private readonly extensionsDownloader: ExtensionsDownloader, - private readonly telemetryService: ITelemetryService, extensionsScanner: ExtensionsScanner, logService: ILogService, ) { super(gallery.identifier, gallery, extensionsScanner, logService); } - install(token: CancellationToken): Promise { - return this.installGalleryExtension(this.gallery, this.options, token); - } - - private async installGalleryExtension(gallery: IGalleryExtension, options: InstallOptions, token: CancellationToken): Promise { - const startTime = new Date().getTime(); - try { - const installed = await this.extensionsScanner.scanExtensions(null); - const existingExtension = installed.find(i => areSameExtensions(i.identifier, gallery.identifier)); - if (existingExtension) { - this._operation = InstallOperation.Update; - } - - const installableExtension = await this.downloadInstallableExtension(gallery, this._operation); - installableExtension.metadata.isMachineScoped = options.isMachineScoped || existingExtension?.isMachineScoped; - installableExtension.metadata.isBuiltin = options.isBuiltin || existingExtension?.isBuiltin; - - const local = await this.installExtension(installableExtension, token); - if (existingExtension && semver.neq(existingExtension.manifest.version, gallery.version)) { - await this.extensionsScanner.setUninstalled(existingExtension); - } - try { await this.extensionsDownloader.delete(URI.file(installableExtension.zipPath)); } catch (error) { /* Ignore */ } - reportTelemetry(this.telemetryService, this.getTelemetryEvent(this._operation), getGalleryExtensionTelemetryData(gallery), new Date().getTime() - startTime, undefined); - return local; - } catch (error) { - reportTelemetry(this.telemetryService, this.getTelemetryEvent(this._operation), getGalleryExtensionTelemetryData(gallery), new Date().getTime() - startTime, error); - throw error; + protected async doRun(token: CancellationToken): Promise { + const installed = await this.extensionsScanner.scanExtensions(null); + const existingExtension = installed.find(i => areSameExtensions(i.identifier, this.gallery.identifier)); + if (existingExtension) { + this._operation = InstallOperation.Update; } - } - private getTelemetryEvent(operation: InstallOperation): string { - return operation === InstallOperation.Update ? 'extensionGallery:update' : 'extensionGallery:install'; + const installableExtension = await this.downloadInstallableExtension(this.gallery, this._operation); + installableExtension.metadata.isMachineScoped = this.options.isMachineScoped || existingExtension?.isMachineScoped; + installableExtension.metadata.isBuiltin = this.options.isBuiltin || existingExtension?.isBuiltin; + + const local = await this.installExtension(installableExtension, token); + if (existingExtension && semver.neq(existingExtension.manifest.version, this.gallery.version)) { + await this.extensionsScanner.setUninstalled(existingExtension); + } + try { await this.extensionsDownloader.delete(URI.file(installableExtension.zipPath)); } catch (error) { /* Ignore */ } + return local; } private async downloadInstallableExtension(extension: IGalleryExtension, operation: InstallOperation): Promise> { @@ -880,16 +306,16 @@ class InstallVSIXTask extends AbstractInstallExtensionTask { constructor( private readonly manifest: IExtensionManifest, - private readonly zipPath: string, + private readonly location: URI, private readonly options: InstallOptions, private readonly galleryService: IExtensionGalleryService, extensionsScanner: ExtensionsScanner, logService: ILogService ) { - super({ id: getGalleryExtensionId(manifest.publisher, manifest.name) }, zipPath, extensionsScanner, logService); + super({ id: getGalleryExtensionId(manifest.publisher, manifest.name) }, location, extensionsScanner, logService); } - async install(token: CancellationToken): Promise { + protected async doRun(token: CancellationToken): Promise { const identifierWithVersion = new ExtensionIdentifierWithVersion(this.identifier, this.manifest.version); const installedExtensions = await this.extensionsScanner.scanExtensions(ExtensionType.User); const existing = installedExtensions.find(i => areSameExtensions(this.identifier, i.identifier)); @@ -921,7 +347,7 @@ class InstallVSIXTask extends AbstractInstallExtensionTask { } } - return this.installExtension({ zipPath: this.zipPath, identifierWithVersion, metadata }, token); + return this.installExtension({ zipPath: path.resolve(this.location.fsPath), identifierWithVersion, metadata }, token); } private async getMetadata(name: string, token: CancellationToken): Promise { @@ -936,3 +362,41 @@ class InstallVSIXTask extends AbstractInstallExtensionTask { return {}; } } + +class UninstallExtensionTask extends AbstractExtensionTask implements IUninstallExtensionTask { + + constructor( + readonly extension: ILocalExtension, + private readonly options: UninstallExtensionTaskOptions, + private readonly extensionsScanner: ExtensionsScanner + ) { super(); } + + protected async doRun(token: CancellationToken): Promise { + const toUninstall: ILocalExtension[] = []; + const userExtensions = await this.extensionsScanner.scanUserExtensions(false); + if (this.options.versionOnly) { + const extensionIdentifierWithVersion = new ExtensionIdentifierWithVersion(this.extension.identifier, this.extension.manifest.version); + toUninstall.push(...userExtensions.filter(u => extensionIdentifierWithVersion.equals(new ExtensionIdentifierWithVersion(u.identifier, u.manifest.version)))); + } else { + toUninstall.push(...userExtensions.filter(u => areSameExtensions(u.identifier, this.extension.identifier))); + } + + if (!toUninstall.length) { + throw new Error(nls.localize('notInstalled', "Extension '{0}' is not installed.", this.extension.manifest.displayName || this.extension.manifest.name)); + } + await this.extensionsScanner.setUninstalled(...toUninstall); + + if (this.options.remove) { + for (const extension of toUninstall) { + try { + if (!token.isCancellationRequested) { + await this.extensionsScanner.removeUninstalledExtension(extension); + } + } catch (e) { + throw new Error(nls.localize('removeError', "Error while removing the extension: {0}. Please Quit and Start VS Code before trying again.", toErrorMessage(e))); + } + } + } + } + +} diff --git a/src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts b/src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts index 5632f259d02..0641561695a 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts @@ -23,7 +23,7 @@ import { ExtensionRecommendation } from 'vs/workbench/contrib/extensions/browser import { ConfigBasedRecommendations } from 'vs/workbench/contrib/extensions/browser/configBasedRecommendations'; import { IExtensionRecommendationNotificationService } from 'vs/platform/extensionRecommendations/common/extensionRecommendations'; import { timeout } from 'vs/base/common/async'; -import { isString } from 'vs/base/common/types'; +import { URI } from 'vs/base/common/uri'; type IgnoreRecommendationClassification = { recommendationReason: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; @@ -216,7 +216,7 @@ export class ExtensionRecommendationsService extends Disposable implements IExte private onDidInstallExtensions(results: readonly InstallExtensionResult[]): void { for (const e of results) { - if (e.source && !isString(e.source) && e.operation === InstallOperation.Install) { + if (e.source && !URI.isUri(e.source) && e.operation === InstallOperation.Install) { const extRecommendations = this.getAllRecommendationsWithReason() || {}; const recommendationReason = extRecommendations[e.source.identifier.id.toLowerCase()]; if (recommendationReason) { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index d90029f4857..7396079d934 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -40,7 +40,7 @@ import { FileAccess } from 'vs/base/common/network'; import { IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions'; import { IUserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataSync'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { isBoolean, isString } from 'vs/base/common/types'; +import { isBoolean } from 'vs/base/common/types'; import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; interface IExtensionStateProvider { @@ -418,7 +418,7 @@ class Extensions extends Disposable { private onInstallExtension(event: InstallExtensionEvent): void { const { source } = event; - if (source && !isString(source)) { + if (source && !URI.isUri(source)) { const extension = this.installed.filter(e => areSameExtensions(e.identifier, source.identifier))[0] || this.instantiationService.createInstance(Extension, this.stateProvider, this.server, undefined, source); this.installing.push(extension); @@ -429,13 +429,13 @@ class Extensions extends Disposable { private onDidInstallExtensions(results: readonly InstallExtensionResult[]): void { for (const event of results) { const { local, source } = event; - const gallery = source && !isString(source) ? source : undefined; - const zipPath = source && isString(source) ? source : undefined; + const gallery = source && !URI.isUri(source) ? source : undefined; + const location = source && URI.isUri(source) ? source : undefined; const installingExtension = gallery ? this.installing.filter(e => areSameExtensions(e.identifier, gallery.identifier))[0] : null; this.installing = installingExtension ? this.installing.filter(e => e !== installingExtension) : this.installing; let extension: Extension | undefined = installingExtension ? installingExtension - : (zipPath || local) ? this.instantiationService.createInstance(Extension, this.stateProvider, this.server, local, undefined) + : (location || local) ? this.instantiationService.createInstance(Extension, this.stateProvider, this.server, local, undefined) : undefined; if (extension) { if (local) { diff --git a/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts index a4043569a9f..de1706998fd 100644 --- a/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts @@ -4,46 +4,30 @@ *--------------------------------------------------------------------------------------------*/ import { ExtensionType, IExtensionIdentifier, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; -import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidUninstallExtensionEvent, IGalleryExtension, IReportedExtension, IGalleryMetadata, InstallOperation, IExtensionGalleryService, ExtensionManagementError, INSTALL_ERROR_INCOMPATIBLE, InstallOptions, InstallExtensionResult } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { Event, Emitter } from 'vs/base/common/event'; +import { IExtensionManagementService, ILocalExtension, IGalleryExtension, IGalleryMetadata, InstallOperation, IExtensionGalleryService, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; import { URI } from 'vs/base/common/uri'; import { areSameExtensions, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IScannedExtension, IWebExtensionsScannerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ILogService } from 'vs/platform/log/common/log'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { localize } from 'vs/nls'; import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IProductService } from 'vs/platform/product/common/productService'; +import { AbstractExtensionManagementService, AbstractExtensionTask, IInstallExtensionTask, IUninstallExtensionTask, UninstallExtensionTaskOptions } from 'vs/platform/extensionManagement/common/abstractExtensionManagementService'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -type Metadata = { - isMachineScoped?: boolean; -}; +type Metadata = Partial; -export class WebExtensionManagementService extends Disposable implements IExtensionManagementService { +export class WebExtensionManagementService extends AbstractExtensionManagementService implements IExtensionManagementService { declare readonly _serviceBrand: undefined; - private readonly _onInstallExtension = this._register(new Emitter()); - readonly onInstallExtension: Event = this._onInstallExtension.event; - - private readonly _onDidInstallExtension = this._register(new Emitter()); - readonly onDidInstallExtensions = this._onDidInstallExtension.event; - - private readonly _onUninstallExtension = this._register(new Emitter()); - readonly onUninstallExtension: Event = this._onUninstallExtension.event; - - private _onDidUninstallExtension = this._register(new Emitter()); - onDidUninstallExtension: Event = this._onDidUninstallExtension.event; - constructor( + @IExtensionGalleryService extensionGalleryService: IExtensionGalleryService, + @ITelemetryService telemetryService: ITelemetryService, + @ILogService logService: ILogService, @IWebExtensionsScannerService private readonly webExtensionsScannerService: IWebExtensionsScannerService, - @ILogService private readonly logService: ILogService, - @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, @IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService, - @IProductService private readonly productService: IProductService, ) { - super(); + super(extensionGalleryService, telemetryService, logService); } async getInstalled(type?: ExtensionType): Promise { @@ -56,15 +40,15 @@ export class WebExtensionManagementService extends Disposable implements IExtens const userExtensions = await this.webExtensionsScannerService.scanUserExtensions(); extensions.push(...userExtensions); } - return Promise.all(extensions.map(e => this.toLocalExtension(e))); + return Promise.all(extensions.map(e => toLocalExtension(e))); } async canInstall(gallery: IGalleryExtension): Promise { - const compatibleExtension = await this.extensionGalleryService.getCompatibleExtension(gallery); + const compatibleExtension = await this.galleryService.getCompatibleExtension(gallery); if (!compatibleExtension) { return false; } - const manifest = await this.extensionGalleryService.getManifest(compatibleExtension, CancellationToken.None); + const manifest = await this.galleryService.getManifest(compatibleExtension, CancellationToken.None); if (!manifest) { return false; } @@ -74,90 +58,99 @@ export class WebExtensionManagementService extends Disposable implements IExtens return true; } - async install(location: URI, installOptions?: InstallOptions): Promise { + async install(location: URI, options: InstallOptions = {}): Promise { + this.logService.trace('ExtensionManagementService#install', location.toString()); const manifest = await this.webExtensionsScannerService.scanExtensionManifest(location); if (!manifest) { throw new Error(`Cannot find packageJSON from the location ${location.toString()}`); } - - const identifier = { id: getGalleryExtensionId(manifest.publisher, manifest.name) }; - this.logService.info('Installing extension:', identifier.id); - this._onInstallExtension.fire({ identifier: identifier, source: location.path }); - - try { - const userExtensions = await this.webExtensionsScannerService.scanUserExtensions(); - const existingExtension = userExtensions.find(e => areSameExtensions(e.identifier, identifier)); - const metadata = this.getMetadata(installOptions, existingExtension); - - const extension = await this.webExtensionsScannerService.addExtension(location, metadata); - const local = this.toLocalExtension(extension); - this._onDidInstallExtension.fire([{ local, identifier: extension.identifier, operation: InstallOperation.Install }]); - return local; - } catch (error) { - this._onDidInstallExtension.fire([{ identifier, operation: InstallOperation.Install }]); - throw error; - } - } - - async installFromGallery(gallery: IGalleryExtension, installOptions?: InstallOptions): Promise { - this.logService.info('Installing extension:', gallery.identifier.id); - this._onInstallExtension.fire({ identifier: gallery.identifier, source: gallery }); - try { - const compatibleExtension = await this.extensionGalleryService.getCompatibleExtension(gallery); - if (!compatibleExtension) { - throw new ExtensionManagementError(localize('notFoundCompatibleDependency', "Unable to install '{0}' extension because it is not compatible with the current version of VS Code (version {1}).", gallery.identifier.id, this.productService.version), INSTALL_ERROR_INCOMPATIBLE); - } - const userExtensions = await this.webExtensionsScannerService.scanUserExtensions(); - const existingExtension = userExtensions.find(e => areSameExtensions(e.identifier, compatibleExtension.identifier)); - const metadata = this.getMetadata(installOptions, existingExtension); - - const scannedExtension = await this.webExtensionsScannerService.addExtensionFromGallery(compatibleExtension, metadata); - const local = this.toLocalExtension(scannedExtension); - this._onDidInstallExtension.fire([{ local, identifier: compatibleExtension.identifier, operation: InstallOperation.Install, source: compatibleExtension }]); - return local; - } catch (error) { - this._onDidInstallExtension.fire([{ identifier: gallery.identifier, operation: InstallOperation.Install, source: gallery }]); - throw error; - } - } - - async uninstall(extension: ILocalExtension): Promise { - this._onUninstallExtension.fire(extension.identifier); - try { - await this.webExtensionsScannerService.removeExtension(extension.identifier); - this._onDidUninstallExtension.fire({ identifier: extension.identifier }); - } catch (error) { - this.logService.error(error); - this._onDidUninstallExtension.fire({ error, identifier: extension.identifier }); - throw error; - } + return this.installExtension(manifest, location, options); } async updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise { return local; } - private toLocalExtension(extension: IScannedExtension): ILocalExtension { - const metadata = this.getMetadata(undefined, extension); - return { - ...extension, - isMachineScoped: !!metadata.isMachineScoped, - publisherId: null, - publisherDisplayName: null, - }; + protected createInstallExtensionTask(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: InstallOptions): IInstallExtensionTask { + return new InstallExtensionTask(manifest, extension, options, this.webExtensionsScannerService); } - private getMetadata(options?: InstallOptions, existingExtension?: IScannedExtension): Metadata { - const metadata: Metadata = {}; - const existingExtensionMetadata: Metadata = existingExtension?.metadata || {}; - metadata.isMachineScoped = options?.isMachineScoped || existingExtensionMetadata.isMachineScoped; - return metadata; + protected createUninstallExtensionTask(extension: ILocalExtension, options: UninstallExtensionTaskOptions): IUninstallExtensionTask { + return new UninstallExtensionTask(extension, options, this.webExtensionsScannerService); } zip(extension: ILocalExtension): Promise { throw new Error('unsupported'); } unzip(zipLocation: URI): Promise { throw new Error('unsupported'); } getManifest(vsix: URI): Promise { throw new Error('unsupported'); } - reinstallFromGallery(extension: ILocalExtension): Promise { throw new Error('unsupported'); } - getExtensionsReport(): Promise { throw new Error('unsupported'); } updateExtensionScope(): Promise { throw new Error('unsupported'); } } + +function toLocalExtension(extension: IScannedExtension): ILocalExtension { + const metadata = getMetadata(undefined, extension); + return { + ...extension, + identifier: { id: extension.identifier.id, uuid: metadata.id }, + isMachineScoped: !!metadata.isMachineScoped, + publisherId: metadata.publisherId || null, + publisherDisplayName: metadata.publisherDisplayName || null, + }; +} + +function getMetadata(options?: InstallOptions, existingExtension?: IScannedExtension): Metadata { + const metadata: Metadata = { ...(existingExtension?.metadata || {}) }; + metadata.isMachineScoped = options?.isMachineScoped || metadata.isMachineScoped; + return metadata; +} + +class InstallExtensionTask extends AbstractExtensionTask implements IInstallExtensionTask { + + readonly identifier: IExtensionIdentifier; + readonly source: URI | IGalleryExtension; + private _operation = InstallOperation.Install; + get operation() { return this._operation; } + + constructor( + manifest: IExtensionManifest, + private readonly extension: URI | IGalleryExtension, + private readonly options: InstallOptions, + private readonly webExtensionsScannerService: IWebExtensionsScannerService, + ) { + super(); + this.identifier = URI.isUri(extension) ? { id: getGalleryExtensionId(manifest.publisher, manifest.name) } : extension.identifier; + this.source = extension; + } + + protected async doRun(token: CancellationToken): Promise { + const userExtensions = await this.webExtensionsScannerService.scanUserExtensions(); + const existingExtension = userExtensions.find(e => areSameExtensions(e.identifier, this.identifier)); + if (existingExtension) { + this._operation = InstallOperation.Update; + } + + const metadata = getMetadata(this.options, existingExtension); + if (!URI.isUri(this.extension)) { + metadata.id = this.extension.identifier.uuid; + metadata.publisherDisplayName = this.extension.publisherDisplayName; + metadata.publisherId = this.extension.publisherId; + } + + const scannedExtension = URI.isUri(this.extension) ? await this.webExtensionsScannerService.addExtension(this.extension, metadata) + : await this.webExtensionsScannerService.addExtensionFromGallery(this.extension, metadata); + return toLocalExtension(scannedExtension); + } +} + +class UninstallExtensionTask extends AbstractExtensionTask implements IUninstallExtensionTask { + + constructor( + readonly extension: ILocalExtension, + options: UninstallExtensionTaskOptions, + private readonly webExtensionsScannerService: IWebExtensionsScannerService, + ) { + super(); + } + + protected doRun(token: CancellationToken): Promise { + return this.webExtensionsScannerService.removeExtension(this.extension.identifier); + } +} diff --git a/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts b/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts index cb714e3f597..7aac4f84772 100644 --- a/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts +++ b/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts @@ -141,7 +141,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten this.logService.info('Ignoring following additional builtin extensions as there is an error while fetching them from gallery', extensionIds, getErrorMessage(error)); } } else { - await this.writeCustomBuiltinExtensionsCache([]); + await this.writeCustomBuiltinExtensionsCache(() => []); } })(), ]); @@ -201,7 +201,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten } try { - await this.writeCustomBuiltinExtensionsCache(webExtensions); + await this.writeCustomBuiltinExtensionsCache(() => webExtensions); } catch (error) { this.logService.info(`Ignoring the error while adding additional builtin gallery extensions`, getErrorMessage(error)); } @@ -307,9 +307,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten } async removeExtension(identifier: IExtensionIdentifier, version?: string): Promise { - let installedExtensions = await this.readInstalledExtensions(); - installedExtensions = installedExtensions.filter(extension => !(areSameExtensions(extension.identifier, identifier) && (version ? extension.version === version : true))); - await this.writeInstalledExtensions(installedExtensions); + await this.writeInstalledExtensions(installedExtensions => installedExtensions.filter(extension => !(areSameExtensions(extension.identifier, identifier) && (version ? extension.version === version : true)))); } private async addWebExtension(webExtension: IWebExtension) { @@ -318,11 +316,12 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten // Update custom builtin extensions to custom builtin extensions cache if (isBuiltin) { - let customBuiltinExtensions = await this.readCustomBuiltinExtensionsCache(); - // Remove the existing extension to avoid duplicates - customBuiltinExtensions = customBuiltinExtensions.filter(extension => !areSameExtensions(extension.identifier, webExtension.identifier)); - customBuiltinExtensions.push(webExtension); - await this.writeCustomBuiltinExtensionsCache(customBuiltinExtensions); + await this.writeCustomBuiltinExtensionsCache(customBuiltinExtensions => { + // Remove the existing extension to avoid duplicates + customBuiltinExtensions = customBuiltinExtensions.filter(extension => !areSameExtensions(extension.identifier, webExtension.identifier)); + customBuiltinExtensions.push(webExtension); + return customBuiltinExtensions; + }); const installedExtensions = await this.readInstalledExtensions(); // Also add to installed extensions if it is installed to update its version @@ -340,11 +339,12 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten } private async addToInstalledExtensions(webExtension: IWebExtension): Promise { - let installedExtensions = await this.readInstalledExtensions(); - // Remove the existing extension to avoid duplicates - installedExtensions = installedExtensions.filter(e => !areSameExtensions(e.identifier, webExtension.identifier)); - installedExtensions.push(webExtension); - await this.writeInstalledExtensions(installedExtensions); + await this.writeInstalledExtensions(installedExtensions => { + // Remove the existing extension to avoid duplicates + installedExtensions = installedExtensions.filter(e => !areSameExtensions(e.identifier, webExtension.identifier)); + installedExtensions.push(webExtension); + return installedExtensions; + }); } private async scanInstalledExtensions(): Promise { @@ -445,29 +445,31 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten } private readInstalledExtensions(): Promise { - return this.readWebExtensions(this.installedExtensionsResource); + return this.withWebExtensions(this.installedExtensionsResource); } - private writeInstalledExtensions(userWebExtensions: IWebExtension[]): Promise { - return this.writeWebExtensions(this.installedExtensionsResource, userWebExtensions); + private writeInstalledExtensions(updateFn: (extensions: IWebExtension[]) => IWebExtension[]): Promise { + return this.withWebExtensions(this.installedExtensionsResource, updateFn); } private readCustomBuiltinExtensionsCache(): Promise { - return this.readWebExtensions(this.customBuiltinExtensionsCacheResource); + return this.withWebExtensions(this.customBuiltinExtensionsCacheResource); } - private writeCustomBuiltinExtensionsCache(customBuiltinExtensions: IWebExtension[]): Promise { - return this.writeWebExtensions(this.customBuiltinExtensionsCacheResource, customBuiltinExtensions); + private writeCustomBuiltinExtensionsCache(updateFn: (extensions: IWebExtension[]) => IWebExtension[]): Promise { + return this.withWebExtensions(this.customBuiltinExtensionsCacheResource, updateFn); } - private async readWebExtensions(file: URI | undefined): Promise { + private async withWebExtensions(file: URI | undefined, updateFn?: (extensions: IWebExtension[]) => IWebExtension[]): Promise { if (!file) { - return []; + throw new Error('unsupported'); } return this.getResourceAccessQueue(file).queue(async () => { + let webExtensions: IWebExtension[] = []; + + // Read try { const content = await this.fileService.readFile(file); - const webExtensions: IWebExtension[] = []; const storedWebExtensions: IStoredWebExtension[] = JSON.parse(content.value.toString()); for (const e of storedWebExtensions) { if (!e.location || !e.identifier || !e.version) { @@ -484,32 +486,28 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten metadata: e.metadata, }); } - return webExtensions; } catch (error) { /* Ignore */ if ((error).fileOperationResult !== FileOperationResult.FILE_NOT_FOUND) { this.logService.error(error); } } - return []; - }); - } - private writeWebExtensions(file: URI | undefined, webExtensions: IWebExtension[]): Promise { - if (!file) { - throw new Error('unsupported'); - } - return this.getResourceAccessQueue(file).queue(async () => { - const storedWebExtensions: IStoredWebExtension[] = webExtensions.map(e => ({ - identifier: e.identifier, - version: e.version, - location: e.location.toJSON(), - readmeUri: e.readmeUri?.toJSON(), - changelogUri: e.changelogUri?.toJSON(), - packageNLSUri: e.packageNLSUri?.toJSON(), - metadata: e.metadata - })); - await this.fileService.writeFile(file, VSBuffer.fromString(JSON.stringify(storedWebExtensions))); + // Update + if (updateFn) { + webExtensions = updateFn(webExtensions); + const storedWebExtensions: IStoredWebExtension[] = webExtensions.map(e => ({ + identifier: e.identifier, + version: e.version, + location: e.location.toJSON(), + readmeUri: e.readmeUri?.toJSON(), + changelogUri: e.changelogUri?.toJSON(), + packageNLSUri: e.packageNLSUri?.toJSON(), + metadata: e.metadata + })); + await this.fileService.writeFile(file, VSBuffer.fromString(JSON.stringify(storedWebExtensions))); + } + return webExtensions; }); } From 2c2314443e2c60149a7b777b2ee7de3fa6db76b2 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 9 Jul 2021 18:04:17 +0200 Subject: [PATCH 13/66] Add assertions for first line --- .../viewModel/viewModelDecorations.test.ts | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/test/common/viewModel/viewModelDecorations.test.ts b/src/vs/editor/test/common/viewModel/viewModelDecorations.test.ts index e1b5683eabb..21d34ce6393 100644 --- a/src/vs/editor/test/common/viewModel/viewModelDecorations.test.ts +++ b/src/vs/editor/test/common/viewModel/viewModelDecorations.test.ts @@ -97,13 +97,37 @@ suite('ViewModelDecorations', () => { 'dec14', ]); - let inlineDecorations1 = viewModel.getViewLineRenderingData( + const inlineDecorations1 = viewModel.getViewLineRenderingData( + new Range(1, viewModel.getLineMinColumn(1), 2, viewModel.getLineMaxColumn(2)), + 1 + ).inlineDecorations; + + // view line 1: (1,1 -> 1,14) + assert.deepStrictEqual(inlineDecorations1, [ + new InlineDecoration(new Range(1, 2, 1, 3), 'i-dec1', InlineDecorationType.Regular), + new InlineDecoration(new Range(1, 2, 1, 2), 'b-dec1', InlineDecorationType.Before), + new InlineDecoration(new Range(1, 3, 1, 3), 'a-dec1', InlineDecorationType.After), + new InlineDecoration(new Range(1, 2, 1, 14), 'i-dec2', InlineDecorationType.Regular), + new InlineDecoration(new Range(1, 2, 1, 2), 'b-dec2', InlineDecorationType.Before), + new InlineDecoration(new Range(1, 14, 1, 14), 'a-dec2', InlineDecorationType.After), + new InlineDecoration(new Range(1, 2, 2, 2), 'i-dec3', InlineDecorationType.Regular), + new InlineDecoration(new Range(1, 2, 1, 2), 'b-dec3', InlineDecorationType.Before), + new InlineDecoration(new Range(1, 2, 3, 13), 'i-dec4', InlineDecorationType.Regular), + new InlineDecoration(new Range(1, 2, 1, 2), 'b-dec4', InlineDecorationType.Before), + new InlineDecoration(new Range(1, 2, 5, 8), 'i-dec5', InlineDecorationType.Regular), + new InlineDecoration(new Range(1, 2, 1, 2), 'b-dec5', InlineDecorationType.Before), + new InlineDecoration(new Range(1, 14, 1, 14), 'i-dec6', InlineDecorationType.Regular), + new InlineDecoration(new Range(1, 14, 1, 14), 'b-dec6', InlineDecorationType.Before), + new InlineDecoration(new Range(1, 14, 1, 14), 'a-dec6', InlineDecorationType.After), + ]); + + const inlineDecorations2 = viewModel.getViewLineRenderingData( new Range(2, viewModel.getLineMinColumn(2), 3, viewModel.getLineMaxColumn(3)), 2 ).inlineDecorations; // view line 2: (1,14 -> 1,24) - assert.deepStrictEqual(inlineDecorations1, [ + assert.deepStrictEqual(inlineDecorations2, [ new InlineDecoration(new Range(1, 2, 2, 2), 'i-dec3', InlineDecorationType.Regular), new InlineDecoration(new Range(2, 2, 2, 2), 'a-dec3', InlineDecorationType.After), new InlineDecoration(new Range(1, 2, 3, 13), 'i-dec4', InlineDecorationType.Regular), @@ -124,13 +148,13 @@ suite('ViewModelDecorations', () => { new InlineDecoration(new Range(2, 3, 2, 3), 'b-dec12', InlineDecorationType.Before), ]); - let inlineDecorations2 = viewModel.getViewLineRenderingData( + const inlineDecorations3 = viewModel.getViewLineRenderingData( new Range(2, viewModel.getLineMinColumn(2), 3, viewModel.getLineMaxColumn(3)), 3 ).inlineDecorations; // view line 3 (24 -> 36) - assert.deepStrictEqual(inlineDecorations2, [ + assert.deepStrictEqual(inlineDecorations3, [ new InlineDecoration(new Range(1, 2, 3, 13), 'i-dec4', InlineDecorationType.Regular), new InlineDecoration(new Range(3, 13, 3, 13), 'a-dec4', InlineDecorationType.After), new InlineDecoration(new Range(1, 2, 5, 8), 'i-dec5', InlineDecorationType.Regular), From 2a0c4cc1c39ca64302352aaf82efacefff243d42 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 8 Jul 2021 12:39:13 +0200 Subject: [PATCH 14/66] Uses eslint-plugin-header to enforce license headers. Features quick fix to add license header. --- .eslintignore | 1 + .eslintrc.json | 13 ++++++++++++- package.json | 3 ++- yarn.lock | 5 +++++ 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/.eslintignore b/.eslintignore index 8b93a4199e5..6d76bb78eb7 100644 --- a/.eslintignore +++ b/.eslintignore @@ -16,3 +16,4 @@ **/extensions/markdown-language-features/notebook-out/** **/extensions/typescript-basics/test/colorize-fixtures/** **/extensions/**/dist/** +**/extensions/typescript-language-features/test-workspace/** diff --git a/.eslintrc.json b/.eslintrc.json index 51e623e8f1b..0983efcd74f 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -7,7 +7,8 @@ }, "plugins": [ "@typescript-eslint", - "jsdoc" + "jsdoc", + "header" ], "rules": { "constructor-super": "warn", @@ -979,6 +980,16 @@ "xterm*" ] } + ], + "header/header": [ + 2, + "block", + [ + "---------------------------------------------------------------------------------------------", + " * Copyright (c) Microsoft Corporation. All rights reserved.", + " * Licensed under the MIT License. See License.txt in the project root for license information.", + " *--------------------------------------------------------------------------------------------" + ] ] }, "overrides": [ diff --git a/package.json b/package.json index 7d9ea7cf630..f5f264494e5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.59.0", - "distro": "4f9399b417ee98731317fbd4616d6fc287172355", + "distro": "74b4e453554c83bca64754d34278aff04e1d78f9", "author": { "name": "Microsoft Corporation" }, @@ -59,6 +59,7 @@ "dependencies": { "applicationinsights": "1.0.8", "chokidar": "3.5.1", + "eslint-plugin-header": "3.1.1", "graceful-fs": "4.2.6", "http-proxy-agent": "^2.1.0", "https-proxy-agent": "^2.2.3", diff --git a/yarn.lock b/yarn.lock index 598813eab24..471ad47e9c7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3264,6 +3264,11 @@ escape-string-regexp@^2.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== +eslint-plugin-header@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-header/-/eslint-plugin-header-3.1.1.tgz#6ce512432d57675265fac47292b50d1eff11acd6" + integrity sha512-9vlKxuJ4qf793CmeeSrZUvVClw6amtpghq3CuWcB5cUNnWHQhgcqy5eF8oVKFk1G3Y/CbchGfEaw3wiIJaNmVg== + eslint-plugin-jsdoc@^19.1.0: version "19.1.0" resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-19.1.0.tgz#fcc17f0378fdd6ee1c847a79b7211745cb05d014" From 9275d0fc5f2b948fa4fd2685ca739f40c96cc0bd Mon Sep 17 00:00:00 2001 From: rebornix Date: Fri, 9 Jul 2021 09:22:44 -0700 Subject: [PATCH 15/66] fix #128312. --- src/vs/workbench/api/common/extHostComments.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/api/common/extHostComments.ts b/src/vs/workbench/api/common/extHostComments.ts index 22b005c0989..c41f16f2d00 100644 --- a/src/vs/workbench/api/common/extHostComments.ts +++ b/src/vs/workbench/api/common/extHostComments.ts @@ -383,7 +383,7 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo }); const that = this; - this.value = Object.freeze({ + this.value = { get uri() { return that.uri; }, get range() { return that.range; }, set range(value: vscode.Range) { that.range = value; }, @@ -400,7 +400,7 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo dispose: () => { that.dispose(); } - }); + }; } From 6a50dc5ea8cbf0f4cfd8cb70756e8cb89692c2cc Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 9 Jul 2021 12:17:10 +0200 Subject: [PATCH 16/66] Implements render helper. --- .../contrib/inlineCompletions/ghostText.ts | 52 +++++++++++++++++++ .../test/inlineCompletionsProvider.test.ts | 4 +- .../contrib/inlineCompletions/test/utils.ts | 23 +------- 3 files changed, 56 insertions(+), 23 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/ghostText.ts b/src/vs/editor/contrib/inlineCompletions/ghostText.ts index 178431c7b2a..94058c5bc88 100644 --- a/src/vs/editor/contrib/inlineCompletions/ghostText.ts +++ b/src/vs/editor/contrib/inlineCompletions/ghostText.ts @@ -7,6 +7,8 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { Position } from 'vs/editor/common/core/position'; +import { Range, IRange } from 'vs/editor/common/core/range'; export class GhostText { public static equals(a: GhostText | undefined, b: GhostText | undefined): boolean { @@ -25,6 +27,56 @@ export class GhostText { this.parts.length === other.parts.length && this.parts.every((part, index) => part.equals(other.parts[index])); } + + render(text: string, debug: boolean = false): string { + const l = this.lineNumber; + return applyEdits(text, + [ + ...this.parts.map(p => ({ + range: { startLineNumber: l, endLineNumber: l, startColumn: p.column, endColumn: p.column }, + text: debug ? `[${p.lines.join('\n')}]` : p.lines.join('\n') + })), + ] + ); + } +} + +class PositionOffsetTransformer { + private readonly lineStartOffsetByLineIdx: number[]; + + constructor(text: string) { + this.lineStartOffsetByLineIdx = []; + this.lineStartOffsetByLineIdx.push(0); + for (let i = 0; i < text.length; i++) { + if (text.charAt(i) === '\n') { + this.lineStartOffsetByLineIdx.push(i + 1); + } + } + } + + getOffset(position: Position): number { + return this.lineStartOffsetByLineIdx[position.lineNumber - 1] + position.column - 1; + } +} + +function applyEdits(text: string, edits: { range: IRange, text: string }[]): string { + const transformer = new PositionOffsetTransformer(text); + const offsetEdits = edits.map(e => { + const range = Range.lift(e.range); + return ({ + startOffset: transformer.getOffset(range.getStartPosition()), + endOffset: transformer.getOffset(range.getEndPosition()), + text: e.text + }); + }); + + offsetEdits.sort((a, b) => b.startOffset - a.startOffset); + + for (const edit of offsetEdits) { + text = text.substring(0, edit.startOffset) + edit.text + text.substring(edit.endOffset); + } + + return text; } export class GhostTextPart { diff --git a/src/vs/editor/contrib/inlineCompletions/test/inlineCompletionsProvider.test.ts b/src/vs/editor/contrib/inlineCompletions/test/inlineCompletionsProvider.test.ts index 1febbae97e6..9b227c547ee 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/inlineCompletionsProvider.test.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/inlineCompletionsProvider.test.ts @@ -10,7 +10,7 @@ import { Range } from 'vs/editor/common/core/range'; import { InlineCompletionsProvider, InlineCompletionsProviderRegistry } from 'vs/editor/common/modes'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; import { InlineCompletionsModel, inlineCompletionToGhostText } from 'vs/editor/contrib/inlineCompletions/inlineCompletionsModel'; -import { GhostTextContext, MockInlineCompletionsProvider, renderGhostTextToText } from 'vs/editor/contrib/inlineCompletions/test/utils'; +import { GhostTextContext, MockInlineCompletionsProvider } from 'vs/editor/contrib/inlineCompletions/test/utils'; import { ITestCodeEditor, TestCodeEditorCreationOptions, withAsyncTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import sinon = require('sinon'); @@ -30,7 +30,7 @@ suite('Inline Completions', () => { const options = ['prefix', 'subword'] as const; const result = {} as any; for (const option of options) { - result[option] = renderGhostTextToText(inlineCompletionToGhostText({ text: suggestion, range }, tempModel, option), cleanedText); + result[option] = inlineCompletionToGhostText({ text: suggestion, range }, tempModel, option)?.render(cleanedText, true); } tempModel.dispose(); diff --git a/src/vs/editor/contrib/inlineCompletions/test/utils.ts b/src/vs/editor/contrib/inlineCompletions/test/utils.ts index 72226659481..e1e6f61e723 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/utils.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/utils.ts @@ -10,27 +10,8 @@ import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands'; import { Position } from 'vs/editor/common/core/position'; import { ITextModel } from 'vs/editor/common/model'; import { InlineCompletionsProvider, InlineCompletion, InlineCompletionContext } from 'vs/editor/common/modes'; -import { GhostText, GhostTextWidgetModel } from 'vs/editor/contrib/inlineCompletions/ghostText'; +import { GhostTextWidgetModel } from 'vs/editor/contrib/inlineCompletions/ghostText'; import { ITestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; -import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; - -export function renderGhostTextToText(ghostText: GhostText, text: string): string; -export function renderGhostTextToText(ghostText: GhostText | undefined, text: string): string | undefined; -export function renderGhostTextToText(ghostText: GhostText | undefined, text: string): string | undefined { - if (!ghostText) { - return undefined; - } - const l = ghostText.lineNumber; - const tempModel = createTextModel(text); - tempModel.applyEdits( - [ - ...ghostText.parts.map(p => ({ range: { startLineNumber: l, endLineNumber: l, startColumn: p.column, endColumn: p.column }, text: `[${p.lines.join('\n')}]` })), - ] - ); - const value = tempModel.getValue(); - tempModel.dispose(); - return value; -} export class MockInlineCompletionsProvider implements InlineCompletionsProvider { private returnValue: InlineCompletion[] = []; @@ -110,7 +91,7 @@ export class GhostTextContext extends Disposable { const ghostText = this.model?.ghostText; let view: string | undefined; if (ghostText) { - view = renderGhostTextToText(ghostText, this.editor.getValue()); + view = ghostText.render(this.editor.getValue(), true); } else { view = this.editor.getValue(); } From 0032b05716dcaf24cc33e651ba05b22dc0b06271 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 7 Jul 2021 22:43:38 +0200 Subject: [PATCH 17/66] Improves diffing for inline suggestions. --- .../inlineCompletionsModel.ts | 63 ++++++++++++++++++- .../test/inlineCompletionsProvider.test.ts | 6 ++ 2 files changed, 66 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/inlineCompletionsModel.ts index 4b91689814c..fbcc85b7557 100644 --- a/src/vs/editor/contrib/inlineCompletions/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/inlineCompletionsModel.ts @@ -19,7 +19,7 @@ import { ICommandService } from 'vs/platform/commands/common/commands'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { RedoCommand, UndoCommand } from 'vs/editor/browser/editorExtensions'; import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands'; -import { IDiffChange, stringDiff } from 'vs/base/common/diff/diff'; +import { IDiffChange, LcsDiff } from 'vs/base/common/diff/diff'; import { GhostTextWidgetModel, GhostText, BaseGhostTextWidgetModel, GhostTextPart } from 'vs/editor/contrib/inlineCompletions/ghostText'; export class InlineCompletionsModel extends Disposable implements GhostTextWidgetModel { @@ -540,12 +540,12 @@ export function inlineCompletionToGhostText(inlineCompletion: NormalizedInlineCo return new GhostText(lineNumber, parts, 0); } -let lastRequest: { originalValue: string, newValue: string, changes: IDiffChange[] } | undefined = undefined; +let lastRequest: { originalValue: string, newValue: string, changes: readonly IDiffChange[] } | undefined = undefined; function cachingDiff(originalValue: string, newValue: string): readonly IDiffChange[] { if (lastRequest?.originalValue === originalValue && lastRequest?.newValue === newValue) { return lastRequest?.changes; } else { - const changes = stringDiff(originalValue, newValue, false); + const changes = smartDiff(originalValue, newValue); lastRequest = { originalValue, newValue, @@ -555,6 +555,63 @@ function cachingDiff(originalValue: string, newValue: string): readonly IDiffCha } } +/** + * When matching `if ()` with `if (f() = 1) { g(); }`, + * align it like this: `if ( )` + * Not like this: `if ( )` + * Also not like this: `if ( )`. + * + * The parenthesis are preprocessed to ensure that they match correctly. + */ +function smartDiff(originalValue: string, newValue: string): readonly IDiffChange[] { + function getMaxCharCode(val: string): number { + let maxCharCode = 0; + for (let i = 0, len = val.length; i < len; i++) { + const charCode = val.charCodeAt(i); + if (charCode > maxCharCode) { + maxCharCode = charCode; + } + } + return maxCharCode; + } + const maxCharCode = Math.max(getMaxCharCode(originalValue), getMaxCharCode(newValue)); + function getUniqueCharCode(id: number): number { + if (id < 0) { + throw new Error('unexpected'); + } + return maxCharCode + id + 1; + } + + function getElements(source: string): Int32Array { + let level = 0; + let group = 0; + const characters = new Int32Array(source.length); + for (let i = 0, len = source.length; i < len; i++) { + const id = group * 100 + level; + + // TODO support more brackets + if (source[i] === '(') { + characters[i] = getUniqueCharCode(2 * id); + level++; + } else if (source[i] === ')') { + characters[i] = getUniqueCharCode(2 * id + 1); + if (level === 1) { + group++; + } + level = Math.max(level - 1, 0); + } else { + characters[i] = source.charCodeAt(i); + } + } + return characters; + } + + const elements1 = getElements(originalValue); + const elements2 = getElements(newValue); + + return new LcsDiff({ getElements: () => elements1 }, { getElements: () => elements2 }).ComputeDiff(false).changes; +} + export interface LiveInlineCompletion extends NormalizedInlineCompletion { sourceProvider: InlineCompletionsProvider; sourceInlineCompletion: InlineCompletion; diff --git a/src/vs/editor/contrib/inlineCompletions/test/inlineCompletionsProvider.test.ts b/src/vs/editor/contrib/inlineCompletions/test/inlineCompletionsProvider.test.ts index 1febbae97e6..1931790a72d 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/inlineCompletionsProvider.test.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/inlineCompletionsProvider.test.ts @@ -75,6 +75,12 @@ suite('Inline Completions', () => { test('Multi Part Diffing', () => { assert.deepStrictEqual(getOutput('foo[()]', '(x);'), { prefix: undefined, subword: 'foo([x])[;]' }); assert.deepStrictEqual(getOutput('[\tfoo]', '\t\tfoobar'), { prefix: undefined, subword: '\t[\t]foo[bar]' }); + assert.deepStrictEqual(getOutput('[(y ===)]', '(y === 1) { f(); }'), { prefix: undefined, subword: '(y ===[ 1])[ { f(); }]' }); + assert.deepStrictEqual(getOutput('[(y ==)]', '(y === 1) { f(); }'), { prefix: undefined, subword: '(y ==[= 1])[ { f(); }]' }); + }); + + test('Multi Part Diffing 1', () => { + assert.deepStrictEqual(getOutput('[if () ()]', 'if (1 == f()) ()'), { prefix: undefined, subword: 'if ([1 == f()]) ()' }); }); }); From 48e7f7f32d94eca811e0184b19c8119edf7d1f2b Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 8 Jul 2021 12:52:18 +0200 Subject: [PATCH 18/66] Introduces editor.useInjectedText (defaults to true) and changes ghost text implementation to use injected text if not disabled. --- .../inlineCompletions/ghostTextWidget.ts | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/ghostTextWidget.ts b/src/vs/editor/contrib/inlineCompletions/ghostTextWidget.ts index 5f4decb3058..fb015f17e67 100644 --- a/src/vs/editor/contrib/inlineCompletions/ghostTextWidget.ts +++ b/src/vs/editor/contrib/inlineCompletions/ghostTextWidget.ts @@ -25,20 +25,21 @@ import { GhostTextWidgetModel } from 'vs/editor/contrib/inlineCompletions/ghostT import { IModelDeltaDecoration } from 'vs/editor/common/model'; import { LineDecoration } from 'vs/editor/common/viewLayout/lineDecorations'; import { InlineDecorationType } from 'vs/editor/common/viewModel/viewModel'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; const ttPolicy = window.trustedTypes?.createPolicy('editorGhostText', { createHTML: value => value }); export class GhostTextWidget extends Disposable { private disposed = false; - private readonly partsWidget = this._register(new DecorationsWidget(this.editor, this.codeEditorService, this.themeService)); + private readonly partsWidget = this._register(this.instantiationService.createInstance(DecorationsWidget, this.editor)); private readonly additionalLinesWidget = this._register(new AdditionalLinesWidget(this.editor)); private viewMoreContentWidget: ViewMoreLinesContentWidget | undefined = undefined; constructor( private readonly editor: ICodeEditor, private readonly model: GhostTextWidgetModel, - @ICodeEditorService private readonly codeEditorService: ICodeEditorService, - @IThemeService private readonly themeService: IThemeService, + @IInstantiationService private readonly instantiationService: IInstantiationService, ) { super(); @@ -200,7 +201,8 @@ class DecorationsWidget implements IDisposable { constructor( private readonly editor: ICodeEditor, @ICodeEditorService private readonly codeEditorService: ICodeEditorService, - @IThemeService private readonly themeService: IThemeService + @IThemeService private readonly themeService: IThemeService, + @IContextKeyService private readonly contextKeyService: IContextKeyService ) { } @@ -254,6 +256,9 @@ class DecorationsWidget implements IDisposable { }); } + const key = this.contextKeyService.getContextKeyValue('config.editor.useInjectedText'); + const shouldUseInjectedText = key === undefined ? true : !!key; + this.decorationIds = this.editor.deltaDecorations(this.decorationIds, parts.map(p => { currentLinePrefix += line.substring(lastIndex, p.column - 1); lastIndex = p.column - 1; @@ -273,7 +278,10 @@ class DecorationsWidget implements IDisposable { return ({ range: Range.fromPositions(new Position(lineNumber, p.column)), - options: { + options: shouldUseInjectedText ? { + description: 'ghost-text', + after: { content: contentText, inlineClassName: 'ghost-text-decoration' } + } : { ...decorationType.resolve() } }); @@ -486,7 +494,7 @@ registerThemingParticipant((theme, collector) => { const opacity = String(foreground.rgba.a); const color = Color.Format.CSS.format(opaque(foreground))!; - // We need to override the only used token type .mtk1 + collector.addRule(`.monaco-editor .ghost-text-decoration { opacity: ${opacity}; color: ${color}; }`); collector.addRule(`.monaco-editor .suggest-preview-text .ghost-text { opacity: ${opacity}; color: ${color}; }`); } From 966eaaee01d571384dc261b76eea9d2f728c0d9e Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 9 Jul 2021 18:46:30 +0200 Subject: [PATCH 19/66] prepare for vacation --- .github/classifier.json | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/classifier.json b/.github/classifier.json index fd27dc85ec6..2c4808df464 100644 --- a/.github/classifier.json +++ b/.github/classifier.json @@ -64,12 +64,12 @@ "extensions": {"assign": ["sandy081"]}, "extensions-development": {"assign": []}, "file-decorations": {"assign": ["jrieken"]}, - "file-encoding": {"assign": ["bpasero"]}, + "file-encoding": {"assign": []}, "file-explorer": {"assign": ["isidorn"]}, "file-glob": {"assign": []}, - "file-guess-encoding": {"assign": ["bpasero"]}, - "file-io": {"assign": ["bpasero"]}, - "file-watcher": {"assign": ["bpasero"]}, + "file-guess-encoding": {"assign": []}, + "file-io": {"assign": []}, + "file-watcher": {"assign": []}, "font-rendering": {"assign": []}, "formatting": {"assign": []}, "git": {"assign": ["eamodio"]}, @@ -150,31 +150,31 @@ "ux": {"assign": ["misolori"]}, "variable-resolving": {"assign": []}, "vscode-build": {"assign": []}, - "web": {"assign": ["bpasero"]}, + "web": {"assign": []}, "webview": {"assign": ["mjbvz"]}, "workbench-cli": {"assign": []}, "workbench-diagnostics": {"assign": ["Tyriar"]}, - "workbench-dnd": {"assign": ["bpasero"]}, + "workbench-dnd": {"assign": []}, "workbench-editor-grid": {"assign": ["sbatten"]}, - "workbench-editors": {"assign": ["bpasero"]}, + "workbench-editors": {"assign": []}, "workbench-electron": {"assign": ["deepak1556"]}, - "workbench-feedback": {"assign": ["bpasero"]}, - "workbench-history": {"assign": ["bpasero"]}, + "workbench-feedback": {"assign": []}, + "workbench-history": {"assign": []}, "workbench-hot-exit": {"assign": []}, "workbench-launch": {"assign": []}, "workbench-link": {"assign": []}, - "workbench-multiroot": {"assign": ["bpasero"]}, - "workbench-notifications": {"assign": ["bpasero"]}, + "workbench-multiroot": {"assign": []}, + "workbench-notifications": {"assign": []}, "workbench-os-integration": {"assign": []}, "workbench-rapid-render": {"assign": ["jrieken"]}, "workbench-run-as-admin": {"assign": []}, - "workbench-state": {"assign": ["bpasero"]}, - "workbench-status": {"assign": ["bpasero"]}, - "workbench-tabs": {"assign": ["bpasero"]}, - "workbench-touchbar": {"assign": ["bpasero"]}, + "workbench-state": {"assign": []}, + "workbench-status": {"assign": []}, + "workbench-tabs": {"assign": []}, + "workbench-touchbar": {"assign": []}, "workbench-views": {"assign": ["sbatten"]}, "workbench-welcome": {"assign": ["JacksonKearl"]}, - "workbench-window": {"assign": ["bpasero"]}, + "workbench-window": {"assign": []}, "workbench-zen": {"assign": ["isidorn"]}, "workspace-edit": {"assign": ["jrieken"]}, "workspace-symbols": {"assign": []}, From 2a82df9437f5c1d3e0302ff4910a5e7f51ea1756 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Fri, 9 Jul 2021 13:08:08 -0400 Subject: [PATCH 20/66] Part of #125422 --- .../contrib/codeEditor/browser/untitledTextEditorHint.ts | 2 +- .../notebook/browser/contrib/outline/notebookOutline.ts | 4 ++-- .../contrib/notebook/browser/diff/notebookDiffActions.ts | 4 ++-- .../contrib/userDataSync/browser/userDataSync.ts | 2 +- .../extensions/electron-browser/extensionService.ts | 2 +- .../workbench/services/host/browser/browserHostService.ts | 2 +- .../common/textResourcePropertiesService.ts | 4 ++-- .../services/themes/browser/workbenchThemeService.ts | 8 ++++---- .../services/untitled/common/untitledTextEditorModel.ts | 2 +- .../services/workspaces/common/workspaceTrust.ts | 4 ++-- src/vs/workbench/test/common/workbenchTestServices.ts | 4 ++-- 11 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/vs/workbench/contrib/codeEditor/browser/untitledTextEditorHint.ts b/src/vs/workbench/contrib/codeEditor/browser/untitledTextEditorHint.ts index dc6d1ecb17c..57b644f5be7 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/untitledTextEditorHint.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/untitledTextEditorHint.ts @@ -47,7 +47,7 @@ export class UntitledTextEditorHintContribution implements IEditorContribution { private update(): void { this.untitledTextHintContentWidget?.dispose(); - const configValue = this.configurationService.getValue<'text' | 'hidden'>(untitledTextEditorHintSetting); + const configValue = this.configurationService.getValue(untitledTextEditorHintSetting); const model = this.editor.getModel(); if (model && model.uri.scheme === Schemas.untitled && model.getModeId() === PLAINTEXT_MODE_ID && configValue === 'text') { diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts b/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts index 0dc0380c832..fb3438d7024 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts @@ -177,7 +177,7 @@ class NotebookOutlineRenderer implements ITreeRenderer(OutlineConfigKeys.problemsBadges); + const useBadges = this._configurationService.getValue(OutlineConfigKeys.problemsBadges); if (!useBadges) { template.decoration.classList.remove('bubble'); template.decoration.innerText = ''; @@ -189,7 +189,7 @@ class NotebookOutlineRenderer implements ITreeRenderer 9 ? '9+' : String(markerInfo.count); } const color = this._themeService.getColorTheme().getColor(markerInfo.topSev === MarkerSeverity.Error ? listErrorForeground : listWarningForeground); - const useColors = this._configurationService.getValue(OutlineConfigKeys.problemsColors); + const useColors = this._configurationService.getValue(OutlineConfigKeys.problemsColors); if (!useColors) { template.container.style.removeProperty('--outline-element-color'); template.decoration.style.setProperty('--outline-element-color', color?.toString() ?? 'inherit'); diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffActions.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffActions.ts index 7c0055bb36f..270d13d1446 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffActions.ts @@ -230,12 +230,12 @@ class ToggleRenderAction extends Action2 { const configurationService = accessor.get(IConfigurationService); if (this.toggleOutputs !== undefined) { - const oldValue = configurationService.getValue('notebook.diff.ignoreOutputs'); + const oldValue = configurationService.getValue('notebook.diff.ignoreOutputs'); configurationService.updateValue('notebook.diff.ignoreOutputs', !oldValue); } if (this.toggleMetadata !== undefined) { - const oldValue = configurationService.getValue('notebook.diff.ignoreMetadata'); + const oldValue = configurationService.getValue('notebook.diff.ignoreMetadata'); configurationService.updateValue('notebook.diff.ignoreMetadata', !oldValue); } } diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index ca730c23966..c6750120870 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -1274,7 +1274,7 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio } if (syncResourceConflicts[1].some(({ remoteResource }) => isEqual(remoteResource, model.uri))) { - return this.configurationService.getValue('diffEditor.renderSideBySide'); + return this.configurationService.getValue('diffEditor.renderSideBySide'); } return false; diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index 917f1aaeb3c..44b1e6022d0 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -111,7 +111,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten } private _isLocalWebWorkerEnabled() { - if (this._configurationService.getValue(webWorkerExtHostConfig)) { + if (this._configurationService.getValue(webWorkerExtHostConfig)) { return true; } if (this._environmentService.isExtensionDevelopment && this._environmentService.extensionDevelopmentKind?.some(k => k === 'web')) { diff --git a/src/vs/workbench/services/host/browser/browserHostService.ts b/src/vs/workbench/services/host/browser/browserHostService.ts index 304e856f940..d98a5111249 100644 --- a/src/vs/workbench/services/host/browser/browserHostService.ts +++ b/src/vs/workbench/services/host/browser/browserHostService.ts @@ -138,7 +138,7 @@ export class BrowserHostService extends Disposable implements IHostService { // Unknown / Keyboard shows veto depending on setting case HostShutdownReason.Unknown: case HostShutdownReason.Keyboard: - const confirmBeforeClose = this.configurationService.getValue<'always' | 'keyboardOnly' | 'never'>('window.confirmBeforeClose'); + const confirmBeforeClose = this.configurationService.getValue('window.confirmBeforeClose'); if (confirmBeforeClose === 'always' || (confirmBeforeClose === 'keyboardOnly' && this.shutdownReason === HostShutdownReason.Keyboard)) { e.veto(true, 'veto.confirmBeforeClose'); } diff --git a/src/vs/workbench/services/textresourceProperties/common/textResourcePropertiesService.ts b/src/vs/workbench/services/textresourceProperties/common/textResourcePropertiesService.ts index 5d2978e9a6b..a655eb51da4 100644 --- a/src/vs/workbench/services/textresourceProperties/common/textResourcePropertiesService.ts +++ b/src/vs/workbench/services/textresourceProperties/common/textResourcePropertiesService.ts @@ -30,8 +30,8 @@ export class TextResourcePropertiesService implements ITextResourcePropertiesSer } getEOL(resource?: URI, language?: string): string { - const eol = this.configurationService.getValue('files.eol', { overrideIdentifier: language, resource }); - if (eol && eol !== 'auto') { + const eol = this.configurationService.getValue('files.eol', { overrideIdentifier: language, resource }); + if (eol && typeof eol === 'string' && eol !== 'auto') { return eol; } const os = this.getOS(resource); diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index 90e4c55366c..27d439869cc 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -380,10 +380,10 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } private getPreferredColorScheme(): ColorScheme | undefined { - if (this.configurationService.getValue(ThemeSettings.DETECT_HC) && this.hostColorService.highContrast) { + if (this.configurationService.getValue(ThemeSettings.DETECT_HC) && this.hostColorService.highContrast) { return ColorScheme.HIGH_CONTRAST; } - if (this.configurationService.getValue(ThemeSettings.DETECT_COLOR_SCHEME)) { + if (this.configurationService.getValue(ThemeSettings.DETECT_COLOR_SCHEME)) { return this.hostColorService.dark ? ColorScheme.DARK : ColorScheme.LIGHT; } return undefined; @@ -391,8 +391,8 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { private async applyPreferredColorTheme(type: ColorScheme): Promise { const settingId = type === ColorScheme.DARK ? ThemeSettings.PREFERRED_DARK_THEME : type === ColorScheme.LIGHT ? ThemeSettings.PREFERRED_LIGHT_THEME : ThemeSettings.PREFERRED_HC_THEME; - const themeSettingId = this.configurationService.getValue(settingId); - if (themeSettingId) { + const themeSettingId = this.configurationService.getValue(settingId); + if (themeSettingId && typeof themeSettingId === 'string') { const theme = this.colorThemeRegistry.findThemeBySettingsId(themeSettingId, undefined); if (theme) { const configurationTarget = this.settings.findAutoConfigurationTarget(settingId); diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts index 68f6f9122c3..952b55eccdb 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts @@ -162,7 +162,7 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt } // Label Format - const configuredLabelFormat = this.textResourceConfigurationService.getValue(this.resource, 'workbench.editor.untitled.labelFormat'); + const configuredLabelFormat = this.textResourceConfigurationService.getValue(this.resource, 'workbench.editor.untitled.labelFormat'); if (this.configuredLabelFormat !== configuredLabelFormat && (configuredLabelFormat === 'content' || configuredLabelFormat === 'name')) { this.configuredLabelFormat = configuredLabelFormat; diff --git a/src/vs/workbench/services/workspaces/common/workspaceTrust.ts b/src/vs/workbench/services/workspaces/common/workspaceTrust.ts index 6d80bb6db64..eb1b6f8fddb 100644 --- a/src/vs/workbench/services/workspaces/common/workspaceTrust.ts +++ b/src/vs/workbench/services/workspaces/common/workspaceTrust.ts @@ -80,7 +80,7 @@ export class WorkspaceTrustEnablementService extends Disposable implements IWork return false; } - return this.configurationService.getValue(WORKSPACE_TRUST_ENABLED) ?? false; + return !!this.configurationService.getValue(WORKSPACE_TRUST_ENABLED); } } @@ -308,7 +308,7 @@ export class WorkspaceTrustManagementService extends Disposable implements IWork } // User setting - return this.configurationService.getValue(WORKSPACE_TRUST_EMPTY_WINDOW) ?? false; + return !!this.configurationService.getValue(WORKSPACE_TRUST_EMPTY_WINDOW); } return this.getUrisTrust(this.getWorkspaceUris()); diff --git a/src/vs/workbench/test/common/workbenchTestServices.ts b/src/vs/workbench/test/common/workbenchTestServices.ts index f4450ecfcd5..4cf874d925d 100644 --- a/src/vs/workbench/test/common/workbenchTestServices.ts +++ b/src/vs/workbench/test/common/workbenchTestServices.ts @@ -34,8 +34,8 @@ export class TestTextResourcePropertiesService implements ITextResourcePropertie } getEOL(resource: URI, language?: string): string { - const eol = this.configurationService.getValue('files.eol', { overrideIdentifier: language, resource }); - if (eol && eol !== 'auto') { + const eol = this.configurationService.getValue('files.eol', { overrideIdentifier: language, resource }); + if (eol && typeof eol === 'string' && eol !== 'auto') { return eol; } return (isLinux || isMacintosh) ? '\n' : '\r\n'; From c9b312035ad87791ad9fe75e86ae531fb06d5d56 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Fri, 9 Jul 2021 13:55:32 -0400 Subject: [PATCH 21/66] More parts of #125422 --- .../contrib/tasks/browser/abstractTaskService.ts | 2 +- src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts | 3 ++- .../contrib/terminal/browser/links/terminalLink.ts | 2 +- .../welcome/walkThrough/browser/walkThroughPart.ts | 6 +++--- src/vs/workbench/electron-sandbox/window.ts | 9 +++++---- .../services/editor/common/editorGroupsService.ts | 2 +- .../services/experiment/common/experimentService.ts | 3 ++- .../browser/extensionEnablementService.ts | 2 +- .../services/extensions/browser/extensionUrlHandler.ts | 2 +- 9 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index b36a2512d4e..5aa40c75ea2 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -2885,7 +2885,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return Promise.resolve(undefined); } content = pickTemplateResult.content; - let editorConfig = this.configurationService.getValue(); + let editorConfig = this.configurationService.getValue() as any; if (editorConfig.editor.insertSpaces) { content = content.replace(/(\n)(\t+)/g, (_, s1, s2) => s1 + ' '.repeat(s2.length * editorConfig.editor.tabSize)); } diff --git a/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts b/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts index 36f392d987b..517811539b9 100644 --- a/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts +++ b/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts @@ -55,7 +55,8 @@ export class TaskQuickPick extends Disposable { } private showDetail(): boolean { - return this.configurationService.getValue(QUICKOPEN_DETAIL_CONFIG); + // Ensure invalid values get converted into boolean values + return !!this.configurationService.getValue(QUICKOPEN_DETAIL_CONFIG); } private guessTaskLabel(task: Task | ConfiguringTask): string { diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalLink.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalLink.ts index 9b3530e26fb..e1421365a86 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalLink.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalLink.ts @@ -91,7 +91,7 @@ export class TerminalLink extends DisposableStore implements ILink { // Clear out scheduler until next hover event this._tooltipScheduler?.dispose(); this._tooltipScheduler = undefined; - }, this._configurationService.getValue('workbench.hover.delay')); + }, this._configurationService.getValue('workbench.hover.delay')); this.add(this._tooltipScheduler); this._tooltipScheduler.schedule(); } diff --git a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts index 7b0a63bda5f..04124d77076 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts @@ -248,11 +248,11 @@ export class WalkThroughPart extends EditorPane { } private getArrowScrollHeight() { - let fontSize = this.configurationService.getValue('editor.fontSize'); + let fontSize = this.configurationService.getValue('editor.fontSize'); if (typeof fontSize !== 'number' || fontSize < 1) { fontSize = 12; } - return 3 * fontSize; + return 3 * (fontSize as number); } pageUp() { @@ -469,7 +469,7 @@ export class WalkThroughPart extends EditorPane { private multiCursorModifier() { const labels = UILabelProvider.modifierLabels[OS]; - const value = this.configurationService.getValue('editor.multiCursorModifier'); + const value = this.configurationService.getValue('editor.multiCursorModifier'); const modifier = labels[value === 'ctrlCmd' ? (OS === OperatingSystem.Macintosh ? 'metaKey' : 'ctrlKey') : 'altKey']; const keys = this.content.querySelectorAll('.multi-cursor-modifier'); Array.prototype.forEach.call(keys, (key: Element) => { diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index f089d46206f..a0c1a737d45 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -40,7 +40,7 @@ import { WorkbenchState, IWorkspaceContextService } from 'vs/platform/workspace/ import { coalesce } from 'vs/base/common/arrays'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { assertIsDefined } from 'vs/base/common/types'; +import { assertIsDefined, isArray } from 'vs/base/common/types'; import { IOpenerService, OpenOptions } from 'vs/platform/opener/common/opener'; import { Schemas } from 'vs/base/common/network'; import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; @@ -360,7 +360,7 @@ export class NativeWindow extends Disposable { // or setting is disabled. Also enabled when running with --wait from the command line. const visibleEditorPanes = this.editorService.visibleEditorPanes; if (visibleEditorPanes.length === 0 && this.contextService.getWorkbenchState() === WorkbenchState.EMPTY && !this.environmentService.isExtensionDevelopment) { - const closeWhenEmpty = this.configurationService.getValue('window.closeWhenEmpty'); + const closeWhenEmpty = this.configurationService.getValue('window.closeWhenEmpty'); if (closeWhenEmpty || this.environmentService.args.wait) { this.closeEmptyWindowScheduler.schedule(); } @@ -564,8 +564,9 @@ export class NativeWindow extends Disposable { const actions: Array = []; - const disabled = this.configurationService.getValue('keyboard.touchbar.enabled') === false; - const ignoredItems = this.configurationService.getValue('keyboard.touchbar.ignored') || []; + const disabled = this.configurationService.getValue('keyboard.touchbar.enabled') === false; + const touchbarIgnored = this.configurationService.getValue('keyboard.touchbar.ignored'); + const ignoredItems = isArray(touchbarIgnored) ? touchbarIgnored : []; // Fill actions into groups respecting order this.touchBarDisposables.add(createAndFillInActionBarActions(this.touchBarMenu, undefined, actions)); diff --git a/src/vs/workbench/services/editor/common/editorGroupsService.ts b/src/vs/workbench/services/editor/common/editorGroupsService.ts index 12b5dbe12e2..ccf656ff47b 100644 --- a/src/vs/workbench/services/editor/common/editorGroupsService.ts +++ b/src/vs/workbench/services/editor/common/editorGroupsService.ts @@ -652,7 +652,7 @@ export function isEditorGroup(obj: unknown): obj is IEditorGroup { //#region Editor Group Helpers export function preferredSideBySideGroupDirection(configurationService: IConfigurationService): GroupDirection.DOWN | GroupDirection.RIGHT { - const openSideBySideDirection = configurationService.getValue<'right' | 'down'>('workbench.editor.openSideBySideDirection'); + const openSideBySideDirection = configurationService.getValue('workbench.editor.openSideBySideDirection'); if (openSideBySideDirection === 'down') { return GroupDirection.DOWN; diff --git a/src/vs/workbench/services/experiment/common/experimentService.ts b/src/vs/workbench/services/experiment/common/experimentService.ts index 5d4b4d1e9d9..8f3bb3f8376 100644 --- a/src/vs/workbench/services/experiment/common/experimentService.ts +++ b/src/vs/workbench/services/experiment/common/experimentService.ts @@ -204,7 +204,8 @@ export class ExperimentService implements ITASExperimentService { } // For development purposes, configure the delay until tas local tas treatment ovverrides are available - const overrideDelay = this.configurationService.getValue('experiments.overrideDelay') ?? 0; + const overrideDelaySetting = this.configurationService.getValue('experiments.overrideDelay'); + const overrideDelay = typeof overrideDelaySetting === 'number' ? overrideDelaySetting : 0; this.overrideInitDelay = new Promise(resolve => setTimeout(resolve, overrideDelay)); } diff --git a/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts b/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts index ca4261f50ea..e8aea2c2ab4 100644 --- a/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts +++ b/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts @@ -328,7 +328,7 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench return false; } } else if (server === this.extensionManagementServerService.localExtensionManagementServer) { - const enableLocalWebWorker = this.configurationService.getValue(webWorkerExtHostConfig); + const enableLocalWebWorker = this.configurationService.getValue(webWorkerExtHostConfig); if (enableLocalWebWorker) { // Web extensions are enabled on all configurations return false; diff --git a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts index e141186c43d..018c5e71474 100644 --- a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts +++ b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts @@ -327,7 +327,7 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { } private getConfirmedTrustedExtensionIdsFromConfiguration(): Array { - const trustedExtensionIds = this.configurationService.getValue>(USER_TRUSTED_EXTENSIONS_CONFIGURATION_KEY); + const trustedExtensionIds = this.configurationService.getValue(USER_TRUSTED_EXTENSIONS_CONFIGURATION_KEY); if (!Array.isArray(trustedExtensionIds)) { return []; From 49b83455e07eafdc057964b72d124b3c2a709aec Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 9 Jul 2021 20:00:39 +0200 Subject: [PATCH 22/66] Fixes off by one bug. --- src/vs/editor/common/viewModel/splitLinesCollection.ts | 2 +- .../test/common/viewModel/splitLinesCollection.test.ts | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/common/viewModel/splitLinesCollection.ts b/src/vs/editor/common/viewModel/splitLinesCollection.ts index 195b5ea9a4b..069eb422c65 100644 --- a/src/vs/editor/common/viewModel/splitLinesCollection.ts +++ b/src/vs/editor/common/viewModel/splitLinesCollection.ts @@ -1246,7 +1246,7 @@ export class SplitLine implements ISplitLine { let r: string; if (this._lineBreakData.injectionOffsets !== null) { - const injectedTexts = this._lineBreakData.injectionOffsets.map((offset, idx) => new LineInjectedText(0, 0, offset, this._lineBreakData.injectionOptions![idx], 0)); + const injectedTexts = this._lineBreakData.injectionOffsets.map((offset, idx) => new LineInjectedText(0, 0, offset + 1, this._lineBreakData.injectionOptions![idx], 0)); r = LineInjectedText.applyInjectedText(model.getLineContent(modelLineNumber), injectedTexts).substring(startOffset, endOffset); } else { r = model.getValueInRange({ diff --git a/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts index 03abcd630f6..cf2c618c495 100644 --- a/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts +++ b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts @@ -408,6 +408,10 @@ suite('SplitLinesCollection', () => { function assertAllMinimapLinesRenderingData(splitLinesCollection: SplitLinesCollection, all: ITestMinimapLineRenderingData[]): void { let lineCount = all.length; + for (let line = 1; line <= lineCount; line++) { + assert.strictEqual(splitLinesCollection.getViewLineData(line).content, splitLinesCollection.getViewLineContent(line)); + } + for (let start = 1; start <= lineCount; start++) { for (let end = start; end <= lineCount; end++) { let count = end - start + 1; @@ -419,6 +423,7 @@ suite('SplitLinesCollection', () => { expected[i] = (needed[i] ? all[start - 1 + i] : null); } let actual = splitLinesCollection.getViewLinesData(start, end, needed); + assertMinimapLinesRenderingData(actual, expected); // Comment out next line to test all possible combinations break; From a08a485a914d9a221d198c13a8208ca9f851cc31 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Fri, 9 Jul 2021 15:40:02 -0400 Subject: [PATCH 23/66] More parts of #125422 --- src/vs/platform/list/browser/listService.ts | 4 ++-- src/vs/platform/terminal/node/terminalProfiles.ts | 2 +- src/vs/platform/update/electron-main/updateService.win32.ts | 2 +- src/vs/platform/userDataSync/common/keybindingsSync.ts | 6 +++--- src/vs/workbench/browser/actions/developerActions.ts | 2 +- src/vs/workbench/browser/layout.ts | 4 ++-- src/vs/workbench/browser/parts/editor/editorCommands.ts | 6 +++--- src/vs/workbench/browser/parts/editor/editorPane.ts | 2 +- .../electron-sandbox/remoteExtensionManagementService.ts | 2 +- .../services/untitled/common/untitledTextEditorModel.ts | 4 ++-- .../services/userDataSync/common/userDataSyncUtil.ts | 2 +- 11 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index 550827d2b03..dc99d2c80e3 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -1156,7 +1156,7 @@ class WorkbenchTreeInternals { newOptions = { ...newOptions, renderIndentGuides }; } if (e.affectsConfiguration(listSmoothScrolling)) { - const smoothScrolling = Boolean(configurationService.getValue(listSmoothScrolling)); + const smoothScrolling = Boolean(!!configurationService.getValue(listSmoothScrolling)); newOptions = { ...newOptions, smoothScrolling }; } if (e.affectsConfiguration(keyboardNavigationSettingKey)) { @@ -1166,7 +1166,7 @@ class WorkbenchTreeInternals { newOptions = { ...newOptions, automaticKeyboardNavigation: getAutomaticKeyboardNavigation() }; } if (e.affectsConfiguration(horizontalScrollingKey) && options.horizontalScrolling === undefined) { - const horizontalScrolling = Boolean(configurationService.getValue(horizontalScrollingKey)); + const horizontalScrolling = Boolean(!!configurationService.getValue(horizontalScrollingKey)); newOptions = { ...newOptions, horizontalScrolling }; } if (e.affectsConfiguration(treeExpandMode) && options.expandOnlyOnTwistieClick === undefined) { diff --git a/src/vs/platform/terminal/node/terminalProfiles.ts b/src/vs/platform/terminal/node/terminalProfiles.ts index ebb842c80fb..66e9e200f87 100644 --- a/src/vs/platform/terminal/node/terminalProfiles.ts +++ b/src/vs/platform/terminal/node/terminalProfiles.ts @@ -37,7 +37,7 @@ export function detectAvailableProfiles( includeDetectedProfiles, fsProvider, logService, - configurationService.getValue(TerminalSettingId.UseWslProfiles) !== false, + configurationService.getValue(TerminalSettingId.UseWslProfiles) !== false, profiles && typeof profiles === 'object' ? { ...profiles } : configurationService.getValue<{ [key: string]: ITerminalProfileObject }>(TerminalSettingId.ProfilesWindows), typeof defaultProfile === 'string' ? defaultProfile : configurationService.getValue(TerminalSettingId.DefaultProfileWindows), testPwshSourcePaths, diff --git a/src/vs/platform/update/electron-main/updateService.win32.ts b/src/vs/platform/update/electron-main/updateService.win32.ts index 81d832df692..0a901c1f049 100644 --- a/src/vs/platform/update/electron-main/updateService.win32.ts +++ b/src/vs/platform/update/electron-main/updateService.win32.ts @@ -149,7 +149,7 @@ export class Win32UpdateService extends AbstractUpdateService { .then(() => updatePackagePath); }); }).then(packagePath => { - const fastUpdatesEnabled = this.configurationService.getValue('update.enableWindowsBackgroundUpdates'); + const fastUpdatesEnabled = this.configurationService.getValue('update.enableWindowsBackgroundUpdates'); this.availableUpdate = { packagePath }; diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index c0610c0db04..e69eca91b94 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -331,15 +331,15 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem } private syncKeybindingsPerPlatform(): boolean { - let userValue = this.configurationService.inspect('settingsSync.keybindingsPerPlatform').userValue; + let userValue = !!this.configurationService.inspect('settingsSync.keybindingsPerPlatform').userValue; if (userValue !== undefined) { return userValue; } - userValue = this.configurationService.inspect('sync.keybindingsPerPlatform').userValue; + userValue = !!this.configurationService.inspect('sync.keybindingsPerPlatform').userValue; if (userValue !== undefined) { return userValue; } - return this.configurationService.getValue('settingsSync.keybindingsPerPlatform'); + return !!this.configurationService.getValue('settingsSync.keybindingsPerPlatform'); } } diff --git a/src/vs/workbench/browser/actions/developerActions.ts b/src/vs/workbench/browser/actions/developerActions.ts index b12c147f01f..0d032154fe0 100644 --- a/src/vs/workbench/browser/actions/developerActions.ts +++ b/src/vs/workbench/browser/actions/developerActions.ts @@ -207,7 +207,7 @@ class ToggleScreencastModeAction extends Action2 { const event = new StandardKeyboardEvent(e); const shortcut = keybindingService.softDispatch(event, event.target); - if (shortcut || !configurationService.getValue('screencastMode.onlyKeyboardShortcuts')) { + if (shortcut || !configurationService.getValue('screencastMode.onlyKeyboardShortcuts')) { if ( event.ctrlKey || event.altKey || event.metaKey || event.shiftKey || length > 20 diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 93162fac56b..3f91a43463c 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -394,13 +394,13 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi if (!this.state.zenMode.active) { // Statusbar visibility - const newStatusbarHiddenValue = !this.configurationService.getValue(Settings.STATUSBAR_VISIBLE); + const newStatusbarHiddenValue = !this.configurationService.getValue(Settings.STATUSBAR_VISIBLE); if (newStatusbarHiddenValue !== this.state.statusBar.hidden) { this.setStatusBarHidden(newStatusbarHiddenValue, skipLayout); } // Activitybar visibility - const newActivityBarHiddenValue = !this.configurationService.getValue(Settings.ACTIVITYBAR_VISIBLE); + const newActivityBarHiddenValue = !this.configurationService.getValue(Settings.ACTIVITYBAR_VISIBLE); if (newActivityBarHiddenValue !== this.state.activityBar.hidden) { this.setActivityBarHidden(newActivityBarHiddenValue, skipLayout); } diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts index e6f7d0ed4ae..bd7d98cd903 100644 --- a/src/vs/workbench/browser/parts/editor/editorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts @@ -352,14 +352,14 @@ function registerDiffEditorCommands(): void { function toggleDiffSideBySide(accessor: ServicesAccessor): void { const configurationService = accessor.get(IConfigurationService); - const newValue = !configurationService.getValue('diffEditor.renderSideBySide'); + const newValue = !configurationService.getValue('diffEditor.renderSideBySide'); configurationService.updateValue('diffEditor.renderSideBySide', newValue); } function toggleDiffIgnoreTrimWhitespace(accessor: ServicesAccessor): void { const configurationService = accessor.get(IConfigurationService); - const newValue = !configurationService.getValue('diffEditor.ignoreTrimWhitespace'); + const newValue = !configurationService.getValue('diffEditor.ignoreTrimWhitespace'); configurationService.updateValue('diffEditor.ignoreTrimWhitespace', newValue); } @@ -948,7 +948,7 @@ function registerOtherEditorCommands(): void { handler: accessor => { const configurationService = accessor.get(IConfigurationService); - const currentSetting = configurationService.getValue('workbench.editor.enablePreview'); + const currentSetting = configurationService.getValue('workbench.editor.enablePreview'); const newSetting = currentSetting === true ? false : true; configurationService.updateValue('workbench.editor.enablePreview', newSetting); } diff --git a/src/vs/workbench/browser/parts/editor/editorPane.ts b/src/vs/workbench/browser/parts/editor/editorPane.ts index 6c5171e11fa..3542418b388 100644 --- a/src/vs/workbench/browser/parts/editor/editorPane.ts +++ b/src/vs/workbench/browser/parts/editor/editorPane.ts @@ -216,7 +216,7 @@ export class EditorMemento implements IEditorMemento { } private updateConfiguration(): void { - this.shareEditorState = this.configurationService.getValue(undefined, 'workbench.editor.sharedViewState') === true; + this.shareEditorState = this.configurationService.getValue(undefined, 'workbench.editor.sharedViewState') === true; } saveEditorState(group: IEditorGroup, resource: URI, state: T): void; diff --git a/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts index 4f0174456c7..d177c9c3e22 100644 --- a/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts @@ -54,7 +54,7 @@ export class NativeRemoteExtensionManagementService extends WebRemoteExtensionMa } private async doInstallFromGallery(extension: IGalleryExtension, installOptions?: InstallOptions): Promise { - if (this.configurationService.getValue('remote.downloadExtensionsLocally')) { + if (this.configurationService.getValue('remote.downloadExtensionsLocally')) { this.logService.trace(`Download '${extension.identifier.id}' extension locally and install`); return this.downloadCompatibleAndInstall(extension); } diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts index 952b55eccdb..c56092e4aaa 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts @@ -152,8 +152,8 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt private onConfigurationChange(fromEvent: boolean): void { // Encoding - const configuredEncoding = this.textResourceConfigurationService.getValue(this.resource, 'files.encoding'); - if (this.configuredEncoding !== configuredEncoding) { + const configuredEncoding = this.textResourceConfigurationService.getValue(this.resource, 'files.encoding'); + if (this.configuredEncoding !== configuredEncoding && typeof configuredEncoding === 'string') { this.configuredEncoding = configuredEncoding; if (fromEvent && !this.preferredEncoding) { diff --git a/src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts b/src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts index 76f7d254f05..4e2a07f036c 100644 --- a/src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts +++ b/src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts @@ -46,7 +46,7 @@ class UserDataSyncUtilService implements IUserDataSyncUtilService { } return { eol: this.textResourcePropertiesService.getEOL(resource), - insertSpaces: this.textResourceConfigurationService.getValue(resource, 'editor.insertSpaces'), + insertSpaces: !!this.textResourceConfigurationService.getValue(resource, 'editor.insertSpaces'), tabSize: this.textResourceConfigurationService.getValue(resource, 'editor.tabSize') }; } From 73f3a2726e73c26da6e52c8a0127246fb08e3395 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 9 Jul 2021 10:05:15 -0700 Subject: [PATCH 24/66] debug: use icon for debug terminals Fixes #128251 --- src/vs/workbench/api/node/extHostDebugService.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/api/node/extHostDebugService.ts b/src/vs/workbench/api/node/extHostDebugService.ts index 9748e2fd654..d7618a4c8e8 100644 --- a/src/vs/workbench/api/node/extHostDebugService.ts +++ b/src/vs/workbench/api/node/extHostDebugService.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import type * as vscode from 'vscode'; import * as platform from 'vs/base/common/platform'; -import { DebugAdapterExecutable } from 'vs/workbench/api/common/extHostTypes'; +import { DebugAdapterExecutable, ThemeIcon } from 'vs/workbench/api/common/extHostTypes'; import { ExecutableDebugAdapter, SocketDebugAdapter, NamedPipeDebugAdapter } from 'vs/workbench/contrib/debug/node/debugAdapter'; import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter'; import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; @@ -98,6 +98,7 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase { shellArgs: shellArgs, cwd: args.cwd, name: terminalName, + iconPath: new ThemeIcon('debug'), }; giveShellTimeToInitialize = true; terminal = this._terminalService.createTerminalFromOptions(options, { From 9044b2c162c8cb5813f58de3157a9bb58ca147f5 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 9 Jul 2021 11:38:44 -0700 Subject: [PATCH 25/66] testing: fix duplicated test items if ext host restarts Fixes #128341 --- src/vs/workbench/api/browser/mainThreadTesting.ts | 5 ++--- .../contrib/testing/common/testServiceImpl.ts | 11 ++++++++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadTesting.ts b/src/vs/workbench/api/browser/mainThreadTesting.ts index b440572b157..960f38b305e 100644 --- a/src/vs/workbench/api/browser/mainThreadTesting.ts +++ b/src/vs/workbench/api/browser/mainThreadTesting.ts @@ -37,7 +37,6 @@ const reviveDiff = (diff: TestsDiff) => { export class MainThreadTesting extends Disposable implements MainThreadTestingShape, ITestRootProvider { private readonly proxy: ExtHostTestingShape; private readonly diffListener = this._register(new MutableDisposable()); - private readonly testSubscriptions = new Map(); private readonly testProviderRegistrations = new Map(); constructor( @@ -205,10 +204,10 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh public override dispose() { super.dispose(); - for (const subscription of this.testSubscriptions.values()) { + for (const subscription of this.testProviderRegistrations.values()) { subscription.dispose(); } - this.testSubscriptions.clear(); + this.testProviderRegistrations.clear(); } private withLiveRun(runId: string, fn: (run: LiveTestResult) => T): T | undefined { diff --git a/src/vs/workbench/contrib/testing/common/testServiceImpl.ts b/src/vs/workbench/contrib/testing/common/testServiceImpl.ts index a9bf1a2ea8a..30f84c9db12 100644 --- a/src/vs/workbench/contrib/testing/common/testServiceImpl.ts +++ b/src/vs/workbench/contrib/testing/common/testServiceImpl.ts @@ -16,7 +16,7 @@ import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/work import { MainThreadTestCollection } from 'vs/workbench/contrib/testing/common/mainThreadTestCollection'; import { MutableObservableValue } from 'vs/workbench/contrib/testing/common/observableValue'; import { StoredValue } from 'vs/workbench/contrib/testing/common/storedValue'; -import { RunTestsRequest, ITestIdWithSrc, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection'; +import { RunTestsRequest, ITestIdWithSrc, TestsDiff, TestDiffOpType } from 'vs/workbench/contrib/testing/common/testCollection'; import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys'; import { ITestResult } from 'vs/workbench/contrib/testing/common/testResult'; import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService'; @@ -195,6 +195,15 @@ export class TestService extends Disposable implements ITestService { this.providerCount.set(this.testControllers.size); return toDisposable(() => { + const diff: TestsDiff = []; + for (const root of this.collection.rootItems) { + if (root.controllerId === id) { + diff.push([TestDiffOpType.Remove, root.item.extId]); + } + } + + this.publishDiff(id, diff); + if (this.testControllers.delete(id)) { this.providerCount.set(this.testControllers.size); } From 126218be8798646ca3b36dd13d4702a380d98b3f Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 9 Jul 2021 12:46:47 -0700 Subject: [PATCH 26/66] main: add a lockfile, and set the user data dir while debugging See https://github.com/microsoft/vscode/issues/127861#issuecomment-877417451 --- .gitignore | 1 + .vscode/launch.json | 2 +- .vscode/settings.json | 1 + src/vs/code/electron-main/main.ts | 16 ++++++++++++++++ .../electron-main/environmentMainService.ts | 4 ++++ 5 files changed, 23 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 11a7486bf53..95f843584c0 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ test-results/ yarn-error.log vscode.lsif vscode.db +/.profile-oss diff --git a/.vscode/launch.json b/.vscode/launch.json index 4e450e9e696..50cd6134a36 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -219,7 +219,7 @@ "cascadeTerminateToConfigurations": [ "Attach to Extension Host" ], - "userDataDir": false, + "userDataDir": "${workspaceFolder}/.profile-oss", "pauseForSourceMap": false, "outFiles": [ "${workspaceFolder}/out/**/*.js" diff --git a/.vscode/settings.json b/.vscode/settings.json index a97841683c0..2c8dce10702 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,6 +4,7 @@ "files.exclude": { ".git": true, ".build": true, + ".profile-oss": true, "**/.DS_Store": true, "build/**/*.js": { "when": "$(basename).ts" diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index c3477d25bc7..34f6d6506b3 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -59,6 +59,7 @@ import { cwd } from 'vs/base/common/process'; import { IProtocolMainService } from 'vs/platform/protocol/electron-main/protocol'; import { ProtocolMainService } from 'vs/platform/protocol/electron-main/protocolMainService'; import { Promises } from 'vs/base/common/async'; +import { toDisposable } from 'vs/base/common/lifecycle'; /** * The main VS Code entry point. @@ -339,6 +340,9 @@ class CodeMain { throw new ExpectedError('Sent env to running instance. Terminating...'); } + const lockFile = await this.createLockfile(environmentMainService); + once(lifecycleMainService.onWillShutdown)(() => lockFile.dispose()); + // Print --status usage info if (environmentMainService.args.status) { logService.warn('Warning: The --status argument can only be used if Code is already running. Please run it again after Code has started.'); @@ -419,6 +423,18 @@ class CodeMain { lifecycleMainService.kill(exitCode); } + private async createLockfile(env: IEnvironmentMainService) { + await FSPromises.writeFile(env.mainLockfile, String(process.pid)); + + return toDisposable(() => { + try { + unlinkSync(env.mainLockfile); + } catch { + // ignored + } + }); + } + //#region Command line arguments utilities private resolveArgs(): NativeParsedArgs { diff --git a/src/vs/platform/environment/electron-main/environmentMainService.ts b/src/vs/platform/environment/electron-main/environmentMainService.ts index 27c94de78d6..d95965be863 100644 --- a/src/vs/platform/environment/electron-main/environmentMainService.ts +++ b/src/vs/platform/environment/electron-main/environmentMainService.ts @@ -30,6 +30,7 @@ export interface IEnvironmentMainService extends INativeEnvironmentService { // --- IPC mainIPCHandle: string; + mainLockfile: string; // --- config sandbox: boolean; @@ -52,6 +53,9 @@ export class EnvironmentMainService extends NativeEnvironmentService implements @memoize get mainIPCHandle(): string { return createStaticIPCHandle(this.userDataPath, 'main', this.productService.version); } + @memoize + get mainLockfile(): string { return join(this.userDataPath, 'code.lock'); } + @memoize get sandbox(): boolean { return !!this.args['__sandbox']; } From fd14f3ebc07f338d785979fd4fb01b3692a9e8b3 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 9 Jul 2021 12:55:07 -0700 Subject: [PATCH 27/66] debug: raise the extension host timeout Fixes 126826 --- .vscode/launch.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 50cd6134a36..8f7e5726510 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -16,7 +16,11 @@ "request": "attach", "restart": true, "name": "Attach to Extension Host", - "timeout": 30000, + // set to a large number: if there is an issue we're debugging that keeps + // the extension host from coming up, or the renderer is paused/crashes + // before it happens, developers will get an annoying alert, e.g. #126826. + // This can be set to 0 in 1.59. + "timeout": 999999999, "port": 5870, "outFiles": [ "${workspaceFolder}/out/**/*.js", From 821e1e577d25690e7119bb017c78d052fcde2f40 Mon Sep 17 00:00:00 2001 From: suema0331 <42561234+suema0331@users.noreply.github.com> Date: Sat, 10 Jul 2021 05:54:30 +0900 Subject: [PATCH 28/66] Fix#122454: Truncate the long terminal title (#122620) --- .../browser/parts/media/compositepart.css | 3 +- .../terminal/browser/media/terminal.css | 10 ++++++ .../contrib/terminal/browser/terminalIcon.ts | 15 ++++++++- .../contrib/terminal/browser/terminalView.ts | 33 +++++++++++++++---- 4 files changed, 53 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/browser/parts/media/compositepart.css b/src/vs/workbench/browser/parts/media/compositepart.css index fde7cdb706c..700544a906a 100644 --- a/src/vs/workbench/browser/parts/media/compositepart.css +++ b/src/vs/workbench/browser/parts/media/compositepart.css @@ -14,4 +14,5 @@ .monaco-workbench .part > .composite.title > .title-actions { flex: 1; padding-left: 5px; -} \ No newline at end of file + overflow: hidden; +} diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index 84224ba5141..8f54a30d4f6 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -374,3 +374,13 @@ .monaco-workbench .pane-body.integrated-terminal .terminal-group > .monaco-split-view2.vertical .terminal-drop-overlay.drop-after { top: 50%; } + +.monaco-workbench .terminal-single-tab .terminal-label-text { + overflow: hidden; + text-overflow: ellipsis; +} + +.monaco-workbench .terminal-single-tab { + min-width: 22px; + +} diff --git a/src/vs/workbench/contrib/terminal/browser/terminalIcon.ts b/src/vs/workbench/contrib/terminal/browser/terminalIcon.ts index a42658df404..8b8356f9f52 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalIcon.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalIcon.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Codicon } from 'vs/base/common/codicons'; +import { Codicon, CSSIcon } from 'vs/base/common/codicons'; import { hash } from 'vs/base/common/hash'; import { URI } from 'vs/base/common/uri'; import { ColorScheme } from 'vs/platform/theme/common/theme'; @@ -55,3 +55,16 @@ export function getIconId(terminal: ITerminalInstance): string { } return terminal.icon.id; } + +const labelWithIconsRegex = new RegExp(`(\\\\)?\\$\\((${CSSIcon.iconNameExpression}(?:${CSSIcon.iconModifierExpression})?)\\)`, 'g'); +export function separateIconAndText(text: string): { icon: string, text: string } { + let match: RegExpMatchArray | null; + let icon = ''; + let textStart = 0; + while ((match = labelWithIconsRegex.exec(text)) !== null) { + textStart = (match.index || 0) + match[0].length; + const [, escaped, codicon] = match; + icon = escaped ? `$(${codicon})` : codicon; + } + return { icon, text: text.substring(textStart) }; +} diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts index 2a8c82a8ce9..a9f09c84c09 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts @@ -33,7 +33,7 @@ import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { TerminalTabbedView } from 'vs/workbench/contrib/terminal/browser/terminalTabbedView'; import { Codicon } from 'vs/base/common/codicons'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; +import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { getColorForSeverity } from 'vs/workbench/contrib/terminal/browser/terminalStatusList'; import { createAndFillInContextMenuActions, MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { TerminalTabContextMenuGroup } from 'vs/workbench/contrib/terminal/browser/terminalMenus'; @@ -41,7 +41,7 @@ import { DropdownWithPrimaryActionViewItem } from 'vs/platform/actions/browser/d import { dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { ColorScheme } from 'vs/platform/theme/common/theme'; -import { getColorClass, getUriClasses } from 'vs/workbench/contrib/terminal/browser/terminalIcon'; +import { getColorClass, getUriClasses, separateIconAndText } from 'vs/workbench/contrib/terminal/browser/terminalIcon'; import { terminalStrings } from 'vs/workbench/contrib/terminal/common/terminalStrings'; import { withNullAsUndefined } from 'vs/base/common/types'; import { DataTransfers } from 'vs/base/browser/dnd'; @@ -407,7 +407,7 @@ class SingleTerminalTabActionViewItem extends MenuEntryActionViewItem { @ITerminalGroupService private readonly _terminalGroupService: ITerminalGroupService, @IThemeService private readonly _themeService: IThemeService, @IContextMenuService private readonly _contextMenuService: IContextMenuService, - @ICommandService private readonly _commandService: ICommandService, + @ICommandService private readonly _commandService: ICommandService ) { super(new MenuItemAction( { @@ -443,6 +443,11 @@ class SingleTerminalTabActionViewItem extends MenuEntryActionViewItem { this._register(toDisposable(() => dispose(this._elementDisposables))); } + override render(container: HTMLElement): void { + super.render(container); + container.classList.add('terminal-single-tab'); + } + override async onClick(event: MouseEvent): Promise { if (event.altKey && this._menuItemAction.alt) { this._commandService.executeCommand(this._menuItemAction.alt.id, { target: TerminalLocation.TerminalView } as ICreateTerminalOptions); @@ -500,7 +505,23 @@ class SingleTerminalTabActionViewItem extends MenuEntryActionViewItem { } } label.style.color = colorStyle; - dom.reset(label, ...renderLabelWithIcons(getSingleTabLabel(instance, ThemeIcon.isThemeIcon(this._commandAction.item.icon) ? this._commandAction.item.icon : undefined))); + + const elements = new Array(); + + const tabLabel = getSingleTabLabel(instance, ThemeIcon.isThemeIcon(this._commandAction.item.icon) ? this._commandAction.item.icon : undefined); + + // ensures that the icon will always be displayed and allows for truncation of text + // to prevent overflow into the actions + const iconAndText = separateIconAndText(tabLabel); + + if (iconAndText.icon) { + elements.push(renderIcon({ id: iconAndText.icon })); + const node = dom.$(`span`); + node.classList.add('terminal-label-text'); + node.innerText = iconAndText.text; + elements.push(node); + dom.reset(label, ...elements); + } if (this._altCommand) { label.classList.remove(this._altCommand); @@ -542,7 +563,7 @@ class SingleTerminalTabActionViewItem extends MenuEntryActionViewItem { } } -function getSingleTabLabel(instance: ITerminalInstance | undefined, icon?: ThemeIcon) { +function getSingleTabLabel(instance: ITerminalInstance | undefined, icon?: ThemeIcon): string { // Don't even show the icon if there is no title as the icon would shift around when the title // is added if (!instance || !instance.title) { @@ -613,7 +634,7 @@ class TerminalThemeIconStyle extends Themable { const iconClasses = getUriClasses(instance, colorTheme.type); if (uri instanceof URI && iconClasses && iconClasses.length > 1) { css += ( - `.monaco-workbench .${iconClasses[0]} .monaco-highlighted-label .codicon, .monaco-action-bar .terminal-uri-icon.single-terminal-tab.action-label:not(.alt-command) .codicon,` + + `.monaco-workbench .${iconClasses[0]} .monaco-highlighted-label .codicon, .monaco-action-bar .terminal-uri-icon.single-terminal-tab.action-label:not(.alt-command) .codicon` + `{background-image: ${dom.asCSSUrl(uri)};}` ); } From 44d2a8d3de0ccd991a0ff4b97134d7d2fe527ad4 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Fri, 9 Jul 2021 13:54:48 -0700 Subject: [PATCH 29/66] switch pat for codespaces prebuild --- .github/workflows/create-codespaces-prebuild.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/create-codespaces-prebuild.yml b/.github/workflows/create-codespaces-prebuild.yml index 4ebac2c4daf..5778874313a 100644 --- a/.github/workflows/create-codespaces-prebuild.yml +++ b/.github/workflows/create-codespaces-prebuild.yml @@ -11,11 +11,11 @@ jobs: run: | $splat = @{ ErrorAction = 'Stop' - Uri = 'https://api.github.com/vscs_internal/user/vscode-triage-bot/codespaces/prebuild' + Uri = 'https://api.github.com/vscs_internal/user/TylerLeonhardt/codespaces/prebuild' Method = 'POST' Headers = @{ 'Content-Type' = 'application/json; charset=utf-8' - 'Authorization' = 'token ${{ secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT }}' + 'Authorization' = 'token ${{ secrets.CODESPACES_PREBUILD_PAT }}' } Body = @{ ref = 'main' From 81a02ec33e26d41974a2a7900ac43d508a69ee05 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 9 Jul 2021 14:09:48 -0700 Subject: [PATCH 30/66] Allow rearranging instances within terminal groups Fixes #128336 --- .../terminal/browser/terminalGroupService.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalGroupService.ts b/src/vs/workbench/contrib/terminal/browser/terminalGroupService.ts index 0a7c693f782..f33d1015823 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalGroupService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalGroupService.ts @@ -326,9 +326,22 @@ export class TerminalGroupService extends Disposable implements ITerminalGroupSe moveGroup(source: ITerminalInstance, target: ITerminalInstance) { const sourceGroup = this.getGroupForInstance(source); const targetGroup = this.getGroupForInstance(target); + + // Something went wrong if (!sourceGroup || !targetGroup) { return; } + + // The groups are the same, rearrange within the group + if (sourceGroup === targetGroup) { + const index = sourceGroup.terminalInstances.indexOf(target); + if (index !== -1) { + sourceGroup.moveInstance(source, index); + } + return; + } + + // The groups differ, rearrange groups const sourceGroupIndex = this.groups.indexOf(sourceGroup); const targetGroupIndex = this.groups.indexOf(targetGroup); this.groups.splice(sourceGroupIndex, 1); From 0609d0bcf83cc07932dcf7da1acc327ba7410e3a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 9 Jul 2021 14:20:29 -0700 Subject: [PATCH 31/66] xterm@4.14.0-beta5 Diff: https://github.com/xtermjs/xterm.js/compare/094bcbd...6ae71ad Fixes #128321 --- package.json | 4 ++-- remote/package.json | 4 ++-- remote/web/package.json | 4 ++-- remote/web/yarn.lock | 16 ++++++++-------- remote/yarn.lock | 16 ++++++++-------- .../contrib/terminal/browser/media/xterm.css | 4 ++++ yarn.lock | 16 ++++++++-------- 7 files changed, 34 insertions(+), 30 deletions(-) diff --git a/package.json b/package.json index f5f264494e5..a1202971de4 100644 --- a/package.json +++ b/package.json @@ -82,10 +82,10 @@ "vscode-ripgrep": "^1.12.0", "vscode-sqlite3": "4.0.11", "vscode-textmate": "5.4.0", - "xterm": "4.13.0", + "xterm": "4.14.0-beta.5", "xterm-addon-search": "0.9.0-beta.3", "xterm-addon-unicode11": "0.3.0-beta.5", - "xterm-addon-webgl": "0.12.0-beta.2", + "xterm-addon-webgl": "0.12.0-beta.4", "yauzl": "^2.9.2", "yazl": "^2.4.3" }, diff --git a/remote/package.json b/remote/package.json index cd726c3cf8d..2abc1e24cab 100644 --- a/remote/package.json +++ b/remote/package.json @@ -22,10 +22,10 @@ "vscode-regexpp": "^3.1.0", "vscode-ripgrep": "^1.12.0", "vscode-textmate": "5.4.0", - "xterm": "4.13.0", + "xterm": "4.14.0-beta.5", "xterm-addon-search": "0.9.0-beta.3", "xterm-addon-unicode11": "0.3.0-beta.5", - "xterm-addon-webgl": "0.12.0-beta.2", + "xterm-addon-webgl": "0.12.0-beta.4", "yauzl": "^2.9.2", "yazl": "^2.4.3" }, diff --git a/remote/web/package.json b/remote/web/package.json index e08e601fbef..c3645f182dc 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -8,9 +8,9 @@ "tas-client-umd": "0.1.4", "vscode-oniguruma": "1.5.1", "vscode-textmate": "5.4.0", - "xterm": "4.13.0", + "xterm": "4.14.0-beta.5", "xterm-addon-search": "0.9.0-beta.3", "xterm-addon-unicode11": "0.3.0-beta.5", - "xterm-addon-webgl": "0.12.0-beta.2" + "xterm-addon-webgl": "0.12.0-beta.4" } } diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index b3de3a15a5f..8309d8bf9b9 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -37,12 +37,12 @@ xterm-addon-unicode11@0.3.0-beta.5: resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.3.0-beta.5.tgz#7e490799d530d3b301125c7a4e92317c161761c4" integrity sha512-SgDDL3PoMH1G48JO6T45whKAex4NPxi80UzUVitnrqyd8dFQP+oF6cxqUutULgm9HSGk62qy3mrZvIMGO5VXog== -xterm-addon-webgl@0.12.0-beta.2: - version "0.12.0-beta.2" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.2.tgz#f9b22c564096629544f78f8fb83f36605e6f37dc" - integrity sha512-v0fiEFLSBSvIw0Be+ddPmtH3LqLJwzIDRArubCPazOVLpS4paFHWyq61cmDZ87Lnb3G+ppMntsocrt273sFYQg== +xterm-addon-webgl@0.12.0-beta.4: + version "0.12.0-beta.4" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.4.tgz#f53d36f2f9c2dc6f6fc040796852c50d4384b351" + integrity sha512-EcxG773wWzlwbowd3bF30mHb/j2ZhHHwHfGutdpQEL3hGBhCqCZOKBqJ/1yRd2N/wKxXEKDfpJt5g5nssiahfw== -xterm@4.13.0: - version "4.13.0" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.13.0.tgz#7998de1e2ad92c4796fe45807be4f31061f3d9d1" - integrity sha512-HVW1gdoLOTnkMaqQCr2r3mQy4fX9iSa5gWxKZ2UTYdLa4iqavv7QxJ8n1Ypse32shPVkhTYPLS6vHEFZp5ghzw== +xterm@4.14.0-beta.5: + version "4.14.0-beta.5" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.14.0-beta.5.tgz#6eb7746c2de288309aa30084ed3bc385901908cc" + integrity sha512-31/sF2RdQbuPx1G7ofpWb0FfOCGDdQRC/r7pxH8sifjHiDu0k3jZsHwIuKOsEwVf6COTlBYyvNe3q7Ex1Htl1g== diff --git a/remote/yarn.lock b/remote/yarn.lock index 76fa44260a6..fe925880435 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -539,15 +539,15 @@ xterm-addon-unicode11@0.3.0-beta.5: resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.3.0-beta.5.tgz#7e490799d530d3b301125c7a4e92317c161761c4" integrity sha512-SgDDL3PoMH1G48JO6T45whKAex4NPxi80UzUVitnrqyd8dFQP+oF6cxqUutULgm9HSGk62qy3mrZvIMGO5VXog== -xterm-addon-webgl@0.12.0-beta.2: - version "0.12.0-beta.2" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.2.tgz#f9b22c564096629544f78f8fb83f36605e6f37dc" - integrity sha512-v0fiEFLSBSvIw0Be+ddPmtH3LqLJwzIDRArubCPazOVLpS4paFHWyq61cmDZ87Lnb3G+ppMntsocrt273sFYQg== +xterm-addon-webgl@0.12.0-beta.4: + version "0.12.0-beta.4" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.4.tgz#f53d36f2f9c2dc6f6fc040796852c50d4384b351" + integrity sha512-EcxG773wWzlwbowd3bF30mHb/j2ZhHHwHfGutdpQEL3hGBhCqCZOKBqJ/1yRd2N/wKxXEKDfpJt5g5nssiahfw== -xterm@4.13.0: - version "4.13.0" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.13.0.tgz#7998de1e2ad92c4796fe45807be4f31061f3d9d1" - integrity sha512-HVW1gdoLOTnkMaqQCr2r3mQy4fX9iSa5gWxKZ2UTYdLa4iqavv7QxJ8n1Ypse32shPVkhTYPLS6vHEFZp5ghzw== +xterm@4.14.0-beta.5: + version "4.14.0-beta.5" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.14.0-beta.5.tgz#6eb7746c2de288309aa30084ed3bc385901908cc" + integrity sha512-31/sF2RdQbuPx1G7ofpWb0FfOCGDdQRC/r7pxH8sifjHiDu0k3jZsHwIuKOsEwVf6COTlBYyvNe3q7Ex1Htl1g== yauzl@^2.9.2: version "2.10.0" diff --git a/src/vs/workbench/contrib/terminal/browser/media/xterm.css b/src/vs/workbench/contrib/terminal/browser/media/xterm.css index 1ac20b3dfdf..018a797e85b 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/xterm.css +++ b/src/vs/workbench/contrib/terminal/browser/media/xterm.css @@ -172,3 +172,7 @@ .xterm-underline { text-decoration: underline; } + +.xterm-strikethrough { + text-decoration: line-through; +} diff --git a/yarn.lock b/yarn.lock index 471ad47e9c7..ec804292bbc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10399,15 +10399,15 @@ xterm-addon-unicode11@0.3.0-beta.5: resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.3.0-beta.5.tgz#7e490799d530d3b301125c7a4e92317c161761c4" integrity sha512-SgDDL3PoMH1G48JO6T45whKAex4NPxi80UzUVitnrqyd8dFQP+oF6cxqUutULgm9HSGk62qy3mrZvIMGO5VXog== -xterm-addon-webgl@0.12.0-beta.2: - version "0.12.0-beta.2" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.2.tgz#f9b22c564096629544f78f8fb83f36605e6f37dc" - integrity sha512-v0fiEFLSBSvIw0Be+ddPmtH3LqLJwzIDRArubCPazOVLpS4paFHWyq61cmDZ87Lnb3G+ppMntsocrt273sFYQg== +xterm-addon-webgl@0.12.0-beta.4: + version "0.12.0-beta.4" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.4.tgz#f53d36f2f9c2dc6f6fc040796852c50d4384b351" + integrity sha512-EcxG773wWzlwbowd3bF30mHb/j2ZhHHwHfGutdpQEL3hGBhCqCZOKBqJ/1yRd2N/wKxXEKDfpJt5g5nssiahfw== -xterm@4.13.0: - version "4.13.0" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.13.0.tgz#7998de1e2ad92c4796fe45807be4f31061f3d9d1" - integrity sha512-HVW1gdoLOTnkMaqQCr2r3mQy4fX9iSa5gWxKZ2UTYdLa4iqavv7QxJ8n1Ypse32shPVkhTYPLS6vHEFZp5ghzw== +xterm@4.14.0-beta.5: + version "4.14.0-beta.5" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.14.0-beta.5.tgz#6eb7746c2de288309aa30084ed3bc385901908cc" + integrity sha512-31/sF2RdQbuPx1G7ofpWb0FfOCGDdQRC/r7pxH8sifjHiDu0k3jZsHwIuKOsEwVf6COTlBYyvNe3q7Ex1Htl1g== y18n@^3.2.1: version "3.2.2" From dca02fb7c6ba74e786ccff72f8329c6b4f26e529 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 9 Jul 2021 14:24:21 -0700 Subject: [PATCH 32/66] Update distro --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a1202971de4..e16e628a9db 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.59.0", - "distro": "74b4e453554c83bca64754d34278aff04e1d78f9", + "distro": "e2cacb542803f4db7885aaa04ae90b834ac37db1", "author": { "name": "Microsoft Corporation" }, From 68d8049feefaae43f4f4281dfe243fecc2be57fe Mon Sep 17 00:00:00 2001 From: rebornix Date: Fri, 9 Jul 2021 14:54:29 -0700 Subject: [PATCH 33/66] fix #128318. --- .../notebook/browser/notebookEditorWidget.ts | 30 ++----------------- 1 file changed, 2 insertions(+), 28 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index b3845ce87a3..540da8eb753 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -33,16 +33,13 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; -import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { contrastBorder, diffInserted, diffRemoved, editorBackground, errorForeground, focusBorder, foreground, listInactiveSelectionBackground, registerColor, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, textBlockQuoteBackground, textBlockQuoteBorder, textLinkActiveForeground, textLinkForeground, textPreformatForeground, transparent } from 'vs/platform/theme/common/colorRegistry'; import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { EditorMemento } from 'vs/workbench/browser/parts/editor/editorPane'; -import { IEditorMemento } from 'vs/workbench/common/editor'; -import { Memento, MementoObject } from 'vs/workbench/common/memento'; import { PANEL_BORDER } from 'vs/workbench/common/theme'; import { debugIconStartForeground } from 'vs/workbench/contrib/debug/browser/debugColors'; -import { CellEditState, CellFocusMode, IActiveNotebookEditor, ICellOutputViewModel, ICellViewModel, ICommonCellInfo, IDisplayOutputLayoutUpdateRequest, IFocusNotebookCellOptions, IGenericCellViewModel, IInsetRenderOutput, INotebookCellList, INotebookCellOutputLayoutInfo, INotebookDeltaDecoration, INotebookEditor, INotebookEditorContribution, INotebookEditorContributionDescription, INotebookEditorCreationOptions, INotebookEditorMouseEvent, INotebookEditorOptions, NotebookLayoutInfo, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_ID, NOTEBOOK_OUTPUT_FOCUSED, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellEditState, CellFocusMode, IActiveNotebookEditor, ICellOutputViewModel, ICellViewModel, ICommonCellInfo, IDisplayOutputLayoutUpdateRequest, IFocusNotebookCellOptions, IGenericCellViewModel, IInsetRenderOutput, INotebookCellList, INotebookCellOutputLayoutInfo, INotebookDeltaDecoration, INotebookEditor, INotebookEditorContribution, INotebookEditorContributionDescription, INotebookEditorCreationOptions, INotebookEditorMouseEvent, INotebookEditorOptions, NotebookLayoutInfo, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_OUTPUT_FOCUSED, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookDecorationCSSRules, NotebookRefCountedStyleSheet } from 'vs/workbench/contrib/notebook/browser/notebookEditorDecorations'; import { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; import { NotebookEditorKernelManager } from 'vs/workbench/contrib/notebook/browser/notebookEditorKernelManager'; @@ -62,7 +59,6 @@ import { CellKind, ExperimentalUseMarkdownRenderer, SelectionStateType } from 'v import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; import { editorGutterModifiedBackground } from 'vs/workbench/contrib/scm/browser/dirtydiffDecorator'; import { Webview } from 'vs/workbench/contrib/webview/browser/webview'; -import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { mark } from 'vs/workbench/contrib/notebook/common/notebookPerformance'; import { readFontInfo } from 'vs/editor/browser/config/configuration'; @@ -73,7 +69,6 @@ import { ViewContext } from 'vs/workbench/contrib/notebook/browser/viewModel/vie import { NotebookEditorToolbar } from 'vs/workbench/contrib/notebook/browser/notebookEditorToolbar'; import { INotebookRendererMessagingService } from 'vs/workbench/contrib/notebook/common/notebookRendererMessagingService'; import { IAckOutputHeight, IMarkupCellInitialization } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages'; -import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; const $ = DOM.$; @@ -210,7 +205,6 @@ export function getDefaultNotebookCreationOptions() { } export class NotebookEditorWidget extends Disposable implements INotebookEditor { - private static readonly EDITOR_MEMENTOS = new Map>(); private _overlayContainer!: HTMLElement; private _notebookTopToolbarContainer!: HTMLElement; private _notebookTopToolbar!: NotebookEditorToolbar; @@ -241,7 +235,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor private _outputRenderer: OutputRenderer; protected readonly _contributions = new Map(); private _scrollBeyondLastLine: boolean; - private readonly _memento: Memento; private readonly _onDidFocusEmitter = this._register(new Emitter()); public readonly onDidFocus = this._onDidFocusEmitter.event; private readonly _onDidBlurEmitter = this._register(new Emitter()); @@ -365,8 +358,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } })); - this._memento = new Memento(NOTEBOOK_EDITOR_ID, storageService); - this._outputRenderer = this._register(new OutputRenderer(this, this.instantiationService)); this._scrollBeyondLastLine = this.configurationService.getValue('editor.scrollBeyondLastLine'); @@ -527,23 +518,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } //#region Editor Core - - protected getEditorMemento(editorGroupService: IEditorGroupsService, configurationService: ITextResourceConfigurationService, key: string, limit: number = 10): IEditorMemento { - const mementoKey = `${NOTEBOOK_EDITOR_ID}${key}`; - - let editorMemento = NotebookEditorWidget.EDITOR_MEMENTOS.get(mementoKey); - if (!editorMemento) { - editorMemento = new EditorMemento(NOTEBOOK_EDITOR_ID, key, this.getMemento(StorageScope.WORKSPACE), limit, editorGroupService, configurationService); - NotebookEditorWidget.EDITOR_MEMENTOS.set(mementoKey, editorMemento); - } - - return editorMemento as IEditorMemento; - } - - protected getMemento(scope: StorageScope): MementoObject { - return this._memento.getMemento(scope, StorageTarget.MACHINE); - } - private _updateForNotebookConfiguration() { if (!this._overlayContainer) { return; From 20790ec994bc0d83f428c08f387628db658828cd Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 9 Jul 2021 14:17:37 -0700 Subject: [PATCH 34/66] testing: increase debugger wait timeout --- test/unit/electron/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/electron/index.js b/test/unit/electron/index.js index d13ea9c6d87..1a707be5e38 100644 --- a/test/unit/electron/index.js +++ b/test/unit/electron/index.js @@ -204,7 +204,7 @@ app.on('ready', () => { timeout = setTimeout(() => { console.error('timed out waiting for before starting tests debugger'); resolve(); - }, 7000); + }, 15000); }).finally(() => { if (socket) { socket.end(); From 4f90d8086538cab199af4c5de33b75b72537db8a Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sat, 10 Jul 2021 08:19:35 +0200 Subject: [PATCH 35/66] files.partcipants.timeout doesn't handle the undefined case (fix #128335) --- .../common/workingCopyFileOperationParticipant.ts | 2 +- src/vs/workbench/test/browser/workbenchTestServices.ts | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyFileOperationParticipant.ts b/src/vs/workbench/services/workingCopy/common/workingCopyFileOperationParticipant.ts index 741f1d51219..c42bfc127e3 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyFileOperationParticipant.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyFileOperationParticipant.ts @@ -29,7 +29,7 @@ export class WorkingCopyFileOperationParticipant extends Disposable { async participate(files: SourceTargetPair[], operation: FileOperation, undoInfo: IFileOperationUndoRedoInfo | undefined, token: CancellationToken): Promise { const timeout = this.configurationService.getValue('files.participants.timeout'); - if (timeout <= 0) { + if (typeof timeout !== 'number' || timeout <= 0) { return; // disabled } diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index caa9961ceb4..32f7056ef51 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -205,7 +205,13 @@ export function workbenchInstantiationService( instantiationService.stub(IProgressService, new TestProgressService()); const workspaceContextService = new TestContextService(TestWorkspace); instantiationService.stub(IWorkspaceContextService, workspaceContextService); - const configService = new TestConfigurationService(); + const configService = new TestConfigurationService({ + files: { + participants: { + timeout: 60000 + } + } + }); instantiationService.stub(IConfigurationService, configService); instantiationService.stub(IFilesConfigurationService, disposables.add(new TestFilesConfigurationService(contextKeyService, configService))); instantiationService.stub(ITextResourceConfigurationService, new TestTextResourceConfigurationService(configService)); From 4ee40e74e1e29178f05f82d026b555490c726772 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sat, 10 Jul 2021 08:23:47 +0200 Subject: [PATCH 36/66] Default presentation of Explorer: Copy Relative Path Separator in Setting editor is confusing (fix #128345) --- src/vs/workbench/contrib/files/browser/fileCommands.ts | 9 ++++++++- .../contrib/files/browser/files.contribution.ts | 7 +++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index d834b54f85a..7a57e7ccb92 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -275,7 +275,14 @@ CommandsRegistry.registerCommand({ async function resourcesToClipboard(resources: URI[], relative: boolean, clipboardService: IClipboardService, labelService: ILabelService, configurationService: IConfigurationService): Promise { if (resources.length) { const lineDelimiter = isWindows ? '\r\n' : '\n'; - const separator = relative ? configurationService.getValue<'/' | '\\' | undefined>('explorer.copyRelativePathSeparator') : undefined; + + let separator: '/' | '\\' | undefined = undefined; + if (relative) { + const relativeSeparator = configurationService.getValue('explorer.copyRelativePathSeparator'); + if (relativeSeparator === '/' || relativeSeparator === '\\') { + separator = relativeSeparator; + } + } const text = resources.map(resource => labelService.getUriLabel(resource, { relative, noPrefix: true, separator })).join(lineDelimiter); await clipboardService.writeText(text); diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index 6b5c6550531..6e87acfd99b 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -407,13 +407,16 @@ configurationRegistry.registerConfiguration({ 'type': 'string', 'enum': [ '/', - '\\' + '\\', + 'auto' ], 'enumDescriptions': [ nls.localize('copyRelativePathSeparator.slash', "Use slash as path separation character."), nls.localize('copyRelativePathSeparator.backslash', "Use backslash as path separation character."), + nls.localize('copyRelativePathSeparator.auto', "Uses operating system specific path separation character."), ], - 'description': nls.localize('copyRelativePathSeparator', "The path separation character used when copying relative file paths. Will use the operating system default unless specified."), + 'description': nls.localize('copyRelativePathSeparator', "The path separation character used when copying relative file paths."), + 'default': 'auto' } } }); From da77887f997f15da82eac4fa1a201e96aabfda82 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sat, 10 Jul 2021 08:56:42 +0200 Subject: [PATCH 37/66] editor memento - make sure to dispose config listener --- src/vs/workbench/browser/parts/editor/editorPane.ts | 10 ++++++---- src/vs/workbench/browser/workbench.contribution.ts | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorPane.ts b/src/vs/workbench/browser/parts/editor/editorPane.ts index 3542418b388..55d2421bd09 100644 --- a/src/vs/workbench/browser/parts/editor/editorPane.ts +++ b/src/vs/workbench/browser/parts/editor/editorPane.ts @@ -19,7 +19,7 @@ import { DEFAULT_EDITOR_MIN_DIMENSIONS, DEFAULT_EDITOR_MAX_DIMENSIONS } from 'vs import { MementoObject } from 'vs/workbench/common/memento'; import { joinPath, IExtUri, isEqual } from 'vs/base/common/resources'; import { indexOfPath } from 'vs/base/common/extpath'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; @@ -159,7 +159,7 @@ export abstract class EditorPane extends Composite implements IEditorPane { let editorMemento = EditorPane.EDITOR_MEMENTOS.get(mementoKey); if (!editorMemento) { - editorMemento = new EditorMemento(this.getId(), key, this.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE), limit, editorGroupService, configurationService); + editorMemento = this._register(new EditorMemento(this.getId(), key, this.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE), limit, editorGroupService, configurationService)); EditorPane.EDITOR_MEMENTOS.set(mementoKey, editorMemento); } @@ -190,7 +190,7 @@ interface MapGroupToMemento { [group: GroupIdentifier]: T; } -export class EditorMemento implements IEditorMemento { +export class EditorMemento extends Disposable implements IEditorMemento { private static readonly SHARED_EDITOR_STATE = -1; // pick a number < 0 to be outside group id range @@ -207,12 +207,14 @@ export class EditorMemento implements IEditorMemento { private editorGroupService: IEditorGroupsService, private configurationService: ITextResourceConfigurationService ) { + super(); + this.updateConfiguration(); this.registerListeners(); } private registerListeners(): void { - this.configurationService.onDidChangeConfiguration(() => this.updateConfiguration()); + this._register(this.configurationService.onDidChangeConfiguration(() => this.updateConfiguration())); } private updateConfiguration(): void { diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index ec9c016b296..32af414acd3 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -194,13 +194,13 @@ const registry = Registry.as(ConfigurationExtensions.Con }, 'workbench.editor.restoreViewState': { 'type': 'boolean', - 'description': localize('restoreViewState', "Restores the last view state (e.g. scroll position) when re-opening editors after they have been closed."), + 'markdownDescription': localize('restoreViewState', "Restores the last editor view state (e.g. scroll position) when re-opening editors after they have been closed. Editor view state is stored per editor group and discarded when a group closes. Use the `#workbench.editor.sharedViewState#` setting to use the last known view state across all editor groups in case no previous view state was found for a editor group."), 'default': true, 'scope': ConfigurationScope.LANGUAGE_OVERRIDABLE }, 'workbench.editor.sharedViewState': { 'type': 'boolean', - 'description': localize('sharedViewState', "Preserves the most recent view state (e.g. scroll position) across all editor groups and restores that if no specific view state is found for the group."), + 'description': localize('sharedViewState', "Preserves the most recent editor view state (e.g. scroll position) across all editor groups and restores that if no specific editor view state is found for the editor group."), 'default': false }, 'workbench.editor.centeredLayoutAutoResize': { From d90f27b521adf7745e688c2613eae6837f7a663a Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 8 Jul 2021 13:11:05 +0200 Subject: [PATCH 38/66] Adds inlineClassNameAffectsLetterSpacing to injected text options. --- src/vs/editor/common/model.ts | 5 +++++ src/vs/editor/common/model/textModel.ts | 2 ++ src/vs/editor/common/viewModel/splitLinesCollection.ts | 6 +++--- src/vs/editor/common/viewModel/viewModel.ts | 5 +++-- src/vs/monaco.d.ts | 4 ++++ 5 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index e33a53efea3..da70d12baf3 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -180,6 +180,11 @@ export interface InjectedTextOptions { * If set, the decoration will be rendered inline with the text with this CSS class name. */ readonly inlineClassName?: string | null; + + /** + * If there is an `inlineClassName` which affects letter spacing. + */ + readonly inlineClassNameAffectsLetterSpacing?: boolean; } /** diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 747b4becd2b..675bee89ef2 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -3399,10 +3399,12 @@ export class ModelDecorationInjectedTextOptions implements model.InjectedTextOpt public readonly content: string; readonly inlineClassName: string | null; + readonly inlineClassNameAffectsLetterSpacing: boolean; private constructor(options: model.InjectedTextOptions) { this.content = options.content || ''; this.inlineClassName = options.inlineClassName || null; + this.inlineClassNameAffectsLetterSpacing = options.inlineClassNameAffectsLetterSpacing || false; } } diff --git a/src/vs/editor/common/viewModel/splitLinesCollection.ts b/src/vs/editor/common/viewModel/splitLinesCollection.ts index 069eb422c65..376e4f280fb 100644 --- a/src/vs/editor/common/viewModel/splitLinesCollection.ts +++ b/src/vs/editor/common/viewModel/splitLinesCollection.ts @@ -1347,13 +1347,13 @@ export class SplitLine implements ISplitLine { if (lineStartOffsetInUnwrappedLine < injectedTextEndOffsetInUnwrappedLine) { // Injected text ends after or in this line (but also starts in or before this line). - const inlineClassName = injectionOptions![i].inlineClassName; - if (inlineClassName) { + const options = injectionOptions![i]; + if (options.inlineClassName) { const offset = (outputLineIndex > 0 ? lineBreakData.wrappedTextIndentLength : 0); const start = offset + Math.max(injectedTextStartOffsetInUnwrappedLine - lineStartOffsetInUnwrappedLine, 0); const end = offset + Math.min(injectedTextEndOffsetInUnwrappedLine - lineStartOffsetInUnwrappedLine, lineEndOffsetInUnwrappedLine); if (start !== end) { - inlineDecorations.push(new SingleLineInlineDecoration(start, end, inlineClassName)); + inlineDecorations.push(new SingleLineInlineDecoration(start, end, options.inlineClassName, options.inlineClassNameAffectsLetterSpacing!)); } } } diff --git a/src/vs/editor/common/viewModel/viewModel.ts b/src/vs/editor/common/viewModel/viewModel.ts index 97feb61ba9c..c95364e88da 100644 --- a/src/vs/editor/common/viewModel/viewModel.ts +++ b/src/vs/editor/common/viewModel/viewModel.ts @@ -557,7 +557,8 @@ export class SingleLineInlineDecoration { constructor( public readonly startOffset: number, public readonly endOffset: number, - public readonly inlineClassName: string + public readonly inlineClassName: string, + public readonly inlineClassNameAffectsLetterSpacing: boolean ) { } @@ -565,7 +566,7 @@ export class SingleLineInlineDecoration { return new InlineDecoration( new Range(lineNumber, this.startOffset + 1, lineNumber, this.endOffset + 1), this.inlineClassName, - InlineDecorationType.Regular + this.inlineClassNameAffectsLetterSpacing ? InlineDecorationType.RegularAffectingLetterSpacing : InlineDecorationType.Regular ); } } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 89b6a013a92..8fe3760dc2c 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -1465,6 +1465,10 @@ declare namespace monaco.editor { * If set, the decoration will be rendered inline with the text with this CSS class name. */ readonly inlineClassName?: string | null; + /** + * If there is an `inlineClassName` which affects letter spacing. + */ + readonly inlineClassNameAffectsLetterSpacing?: boolean; } /** From 66a87470fd0a01e2a037de9f54d2f00363a3d9ab Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 12 Jul 2021 09:58:05 +0200 Subject: [PATCH 39/66] return empty array when not supported --- .../extensionManagement/common/webExtensionsScannerService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts b/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts index 7aac4f84772..25d1e8e05b8 100644 --- a/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts +++ b/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts @@ -462,7 +462,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten private async withWebExtensions(file: URI | undefined, updateFn?: (extensions: IWebExtension[]) => IWebExtension[]): Promise { if (!file) { - throw new Error('unsupported'); + return []; } return this.getResourceAccessQueue(file).queue(async () => { let webExtensions: IWebExtension[] = []; From 98f24bb5b89bb0b68259ed5e4818200ac8aff3aa Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 12 Jul 2021 10:48:38 +0200 Subject: [PATCH 40/66] remove deprecated api --- .../userDataSync/common/userDataAutoSyncService.ts | 4 ++-- .../browser/userDataAutoSyncEnablementService.ts | 2 +- src/vs/workbench/workbench.web.api.ts | 9 --------- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts b/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts index 0d258c8e48c..a7494770538 100644 --- a/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts @@ -61,14 +61,14 @@ export class UserDataAutoSyncEnablementService extends Disposable implements _IU this._register(storageService.onDidChangeValue(e => this.onDidStorageChange(e))); } - isEnabled(defaultEnablement?: boolean): boolean { + isEnabled(): boolean { switch (this.environmentService.sync) { case 'on': return true; case 'off': return false; } - return this.storageService.getBoolean(enablementKey, StorageScope.GLOBAL, !!defaultEnablement); + return this.storageService.getBoolean(enablementKey, StorageScope.GLOBAL, false); } canToggleEnablement(): boolean { diff --git a/src/vs/workbench/services/userDataSync/browser/userDataAutoSyncEnablementService.ts b/src/vs/workbench/services/userDataSync/browser/userDataAutoSyncEnablementService.ts index 7d4c6ffbb1a..049ad925d59 100644 --- a/src/vs/workbench/services/userDataSync/browser/userDataAutoSyncEnablementService.ts +++ b/src/vs/workbench/services/userDataSync/browser/userDataAutoSyncEnablementService.ts @@ -25,7 +25,7 @@ export class WebUserDataAutoSyncEnablementService extends UserDataAutoSyncEnable this.enabled = this.workbenchEnvironmentService.options?.settingsSyncOptions?.enabled; } if (this.enabled === undefined) { - this.enabled = super.isEnabled(this.workbenchEnvironmentService.options?.enableSyncByDefault); + this.enabled = super.isEnabled(); } return this.enabled; } diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index 62fad7c518a..3bf89925138 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -318,15 +318,6 @@ interface IWorkbenchConstructionOptions { */ readonly workspaceProvider?: IWorkspaceProvider; - /** - * Enables Settings Sync by default. - * - * Syncs with the current authenticated user account (provided in [credentialsProvider](#credentialsProvider)) by default. - * - * @deprecated Instead use [settingsSyncOptions](#settingsSyncOptions) to enable/disable settings sync in the workbench. - */ - readonly enableSyncByDefault?: boolean; - /** * Settings sync options */ From d737dc4db37031180d0be4c89060a1b236b76855 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Mon, 12 Jul 2021 12:20:34 +0200 Subject: [PATCH 41/66] smoke: simplify localization test --- .../src/areas/workbench/localization.test.ts | 49 +++++++------------ 1 file changed, 18 insertions(+), 31 deletions(-) diff --git a/test/smoke/src/areas/workbench/localization.test.ts b/test/smoke/src/areas/workbench/localization.test.ts index d6e2320c280..bacd3d7396d 100644 --- a/test/smoke/src/areas/workbench/localization.test.ts +++ b/test/smoke/src/areas/workbench/localization.test.ts @@ -10,45 +10,32 @@ import { afterSuite, beforeSuite } from '../../utils'; export function setup(opts: minimist.ParsedArgs) { describe('Localization', () => { beforeSuite(opts); - - before(async function () { - const app = this.app as Application; - - // Don't run the localization tests in dev or remote. - if (app.quality === Quality.Dev || app.remote) { - return; - } - - await app.workbench.extensions.openExtensionsViewlet(); - await app.workbench.extensions.installExtension('ms-ceintl.vscode-language-pack-de', false); - - await app.restart({ extraArgs: ['--locale=DE'] }); - }); - afterSuite(); it(`starts with 'DE' locale and verifies title and viewlets text is in German`, async function () { const app = this.app as Application; - const result = await app.workbench.localization.getLocalizedStrings(); if (app.quality === Quality.Dev || app.remote) { - if (result.open !== 'open' || result.close !== 'close' || result.find !== 'find') { - throw new Error(`Received wrong localized strings: ${JSON.stringify(result, undefined, 0)}`); - } - return; - } else { - const localeInfo = await app.workbench.localization.getLocaleInfo(); - if (localeInfo.locale === undefined || localeInfo.locale.toLowerCase() !== 'de') { - throw new Error(`The requested locale for VS Code was not German. The received value is: ${localeInfo.locale === undefined ? 'not set' : localeInfo.locale}`); - } + return this.skip(); + } - if (localeInfo.language.toLowerCase() !== 'de') { - throw new Error(`The UI language is not German. It is ${localeInfo.language}`); - } + await app.workbench.extensions.openExtensionsViewlet(); + await app.workbench.extensions.installExtension('ms-ceintl.vscode-language-pack-de', false); + await app.restart({ extraArgs: ['--locale=DE'] }); - if (result.open.toLowerCase() !== 'öffnen' || result.close.toLowerCase() !== 'schließen' || result.find.toLowerCase() !== 'finden') { - throw new Error(`Received wrong German localized strings: ${JSON.stringify(result, undefined, 0)}`); - } + const result = await app.workbench.localization.getLocalizedStrings(); + const localeInfo = await app.workbench.localization.getLocaleInfo(); + + if (localeInfo.locale === undefined || localeInfo.locale.toLowerCase() !== 'de') { + throw new Error(`The requested locale for VS Code was not German. The received value is: ${localeInfo.locale === undefined ? 'not set' : localeInfo.locale}`); + } + + if (localeInfo.language.toLowerCase() !== 'de') { + throw new Error(`The UI language is not German. It is ${localeInfo.language}`); + } + + if (result.open.toLowerCase() !== 'öffnen' || result.close.toLowerCase() !== 'schließen' || result.find.toLowerCase() !== 'finden') { + throw new Error(`Received wrong German localized strings: ${JSON.stringify(result, undefined, 0)}`); } }); }); From 72a99c237dbb947b3916e95cc088727dbe696d3c Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 12 Jul 2021 12:35:30 +0200 Subject: [PATCH 42/66] Improves leaking disposables error message. --- src/vs/base/test/common/utils.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/vs/base/test/common/utils.ts b/src/vs/base/test/common/utils.ts index 2c387dab46f..6472e73dac0 100644 --- a/src/vs/base/test/common/utils.ts +++ b/src/vs/base/test/common/utils.ts @@ -96,7 +96,17 @@ class DisposableTracker implements IDisposableTracker { .filter(v => v.source !== null && !this.getRootParent(v, rootParentCache).isSingleton); if (leaking.length > 0) { - throw new Error(`These disposables were not disposed:\n${leaking.map(l => l.source).join('--------------------\n')}`); + const count = 10; + const firstLeaking = leaking.slice(0, count); + const remainingCount = leaking.length - count; + + const separator = '--------------------\n\n'; + let s = firstLeaking.map(l => l.source).join(separator); + if (remainingCount > 0) { + s += `${separator}+ ${remainingCount} more`; + } + + throw new Error(`These disposables were not disposed:\n${s}`); } } } From 626e7f2d39bdecf1fe7e3897467930ea81fe6cba Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 12 Jul 2021 14:09:08 +0200 Subject: [PATCH 43/66] Dispose disposables in tests --- .../base/parts/ipc/test/node/ipc.net.test.ts | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/vs/base/parts/ipc/test/node/ipc.net.test.ts b/src/vs/base/parts/ipc/test/node/ipc.net.test.ts index d572f52f1f5..9aec2bbceed 100644 --- a/src/vs/base/parts/ipc/test/node/ipc.net.test.ts +++ b/src/vs/base/parts/ipc/test/node/ipc.net.test.ts @@ -11,19 +11,22 @@ import { createRandomIPCHandle, createStaticIPCHandle, NodeSocket } from 'vs/bas import { VSBuffer } from 'vs/base/common/buffer'; import { tmpdir } from 'os'; import product from 'vs/platform/product/common/product'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { Disposable } from 'vs/base/common/lifecycle'; -class MessageStream { +class MessageStream extends Disposable { private _currentComplete: ((data: VSBuffer) => void) | null; private _messages: VSBuffer[]; constructor(x: Protocol | PersistentProtocol) { + super(); this._currentComplete = null; this._messages = []; - x.onMessage(data => { + this._register(x.onMessage(data => { this._messages.push(data); this._trigger(); - }); + })); } private _trigger(): void { @@ -121,6 +124,8 @@ class Ether { suite('IPC, Socket Protocol', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + let ether: Ether; setup(() => { @@ -142,6 +147,10 @@ suite('IPC, Socket Protocol', () => { a.send(buffer); const msg2 = await bMessages.waitForOne(); assert.strictEqual(msg2.readUInt8(0), 123); + + bMessages.dispose(); + a.dispose(); + b.dispose(); }); @@ -161,11 +170,18 @@ suite('IPC, Socket Protocol', () => { a.send(VSBuffer.fromString(JSON.stringify(data))); const msg = await bMessages.waitForOne(); assert.deepStrictEqual(JSON.parse(msg.toString()), data); + + bMessages.dispose(); + a.dispose(); + b.dispose(); }); }); suite('PersistentProtocol reconnection', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + let ether: Ether; setup(() => { @@ -222,6 +238,11 @@ suite('PersistentProtocol reconnection', () => { assert.strictEqual(b2.toString(), 'a4'); assert.strictEqual(a.unacknowledgedCount, 1); assert.strictEqual(b.unacknowledgedCount, 0); + + aMessages.dispose(); + bMessages.dispose(); + a.dispose(); + b.dispose(); }); }); From e04fdf00049c9001026c4c454a6b0b26df904e4d Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 12 Jul 2021 14:36:54 +0200 Subject: [PATCH 44/66] Fix #128375 --- .../extensionManagement/browser/extensionEnablementService.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts b/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts index e8aea2c2ab4..60fdd1b3824 100644 --- a/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts +++ b/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts @@ -367,7 +367,8 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench } try { for (const dependencyExtension of dependencyExtensions) { - if (!this.isEnabledEnablementState(this._computeEnablementState(dependencyExtension, extensions, workspaceType, computedEnablementStates))) { + const enablementState = this._computeEnablementState(dependencyExtension, extensions, workspaceType, computedEnablementStates); + if (!this.isEnabledEnablementState(enablementState) && enablementState !== EnablementState.DisabledByExtensionKind) { return true; } } From 774c871eba5502955354a20d3122238750085552 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 12 Jul 2021 14:37:12 +0200 Subject: [PATCH 45/66] #128375 add tests --- .../browser/extensionEnablementService.test.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts b/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts index cfec9d2aae3..771355aa855 100644 --- a/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts +++ b/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts @@ -769,6 +769,21 @@ suite('ExtensionEnablementService Test', () => { assert.strictEqual(testObject.getEnablementState(installed[1]), EnablementState.DisabledByExtensionDependency); }); + test('test extension is not disabled by dependency if it has a dependency that is disabled by extension kind', async () => { + instantiationService.stub(IExtensionManagementServerService, anExtensionManagementServerService(anExtensionManagementServer('vscode-local', instantiationService), anExtensionManagementServer('vscode-remote', instantiationService), null)); + const localUIExtension = aLocalExtension2('pub.a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`) }); + const remoteUIExtension = aLocalExtension2('pub.a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const remoteWorkspaceExtension = aLocalExtension2('pub.n', { extensionKind: ['workspace'], extensionDependencies: ['pub.a'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + installed.push(localUIExtension, remoteUIExtension, remoteWorkspaceExtension); + + testObject = new TestExtensionEnablementService(instantiationService); + await (testObject).waitUntilInitialized(); + + assert.strictEqual(testObject.getEnablementState(localUIExtension), EnablementState.EnabledGlobally); + assert.strictEqual(testObject.getEnablementState(remoteUIExtension), EnablementState.DisabledByExtensionKind); + assert.strictEqual(testObject.getEnablementState(remoteWorkspaceExtension), EnablementState.EnabledGlobally); + }); + test('test canChangeEnablement return true when extension is disabled by dependency if it has a dependency that is disabled by workspace trust', async () => { installed.push(...[aLocalExtension2('pub.a', { main: 'hello.js', capabilities: { untrustedWorkspaces: { supported: false, description: '' } } }), aLocalExtension2('pub.b', { extensionDependencies: ['pub.a'], capabilities: { untrustedWorkspaces: { supported: true } } })]); instantiationService.stub(IWorkspaceTrustManagementService, >{ isWorkspaceTrusted() { return false; } }); From d85bc647efedb108a7a59d5e27655de0b0241de3 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 12 Jul 2021 14:44:50 +0200 Subject: [PATCH 46/66] smoke tests - automatically test against stable build (#127799) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * smoke tests - automatically test against stable build * fix path on Linux/Windows * :lipstick: * retry migration tests * smoke: always download latest previous stable * hm Co-authored-by: João Moreno --- test/smoke/.gitignore | 3 +- test/smoke/package.json | 3 + .../areas/workbench/data-migration.test.ts | 12 +- test/smoke/src/main.ts | 79 +++++- test/smoke/yarn.lock | 252 +++++++++++++++++- 5 files changed, 330 insertions(+), 19 deletions(-) diff --git a/test/smoke/.gitignore b/test/smoke/.gitignore index d7ed700e659..2f8cb79f0ea 100644 --- a/test/smoke/.gitignore +++ b/test/smoke/.gitignore @@ -6,4 +6,5 @@ out/ keybindings.*.json test_data/ src/vscode/driver.d.ts -vscode-server*/ \ No newline at end of file +vscode-server*/ +.vscode-test diff --git a/test/smoke/package.json b/test/smoke/package.json index 9982eea55db..fac85ddf129 100644 --- a/test/smoke/package.json +++ b/test/smoke/package.json @@ -16,16 +16,19 @@ "@types/mocha": "^8.2.0", "@types/ncp": "2.0.1", "@types/node": "14.x", + "@types/node-fetch": "^2.5.10", "@types/rimraf": "^2.0.4", "htmlparser2": "^3.9.2", "mkdirp": "^1.0.4", "ncp": "^2.0.0", + "node-fetch": "^2.6.1", "npm-run-all": "^4.1.5", "portastic": "^1.0.1", "rimraf": "^2.6.1", "strip-json-comments": "^2.0.1", "tmp": "0.0.33", "typescript": "^4.3.2", + "vscode-test": "^1.5.2", "watch": "^1.0.2" } } diff --git a/test/smoke/src/areas/workbench/data-migration.test.ts b/test/smoke/src/areas/workbench/data-migration.test.ts index 63929912e55..d3af6f80797 100644 --- a/test/smoke/src/areas/workbench/data-migration.test.ts +++ b/test/smoke/src/areas/workbench/data-migration.test.ts @@ -5,15 +5,24 @@ import { Application, ApplicationOptions, Quality } from '../../../../automation'; import { join } from 'path'; +import { ParsedArgs } from 'minimist'; -export function setup(stableCodePath: string, testDataPath: string) { +export function setup(opts: ParsedArgs, testDataPath: string) { describe('Datamigration', () => { it(`verifies opened editors are restored`, async function () { + const stableCodePath = opts['stable-build']; if (!stableCodePath) { this.skip(); } + // On macOS, the stable app fails to launch on first try, + // so let's retry this once + // https://github.com/microsoft/vscode/pull/127799 + if (process.platform === 'darwin') { + this.retries(2); + } + const userDataDir = join(testDataPath, 'd2'); // different data dir from the other tests const stableOptions: ApplicationOptions = Object.assign({}, this.defaultOptions); @@ -50,6 +59,7 @@ export function setup(stableCodePath: string, testDataPath: string) { }); it(`verifies that 'hot exit' works for dirty files`, async function () { + const stableCodePath = opts['stable-build']; if (!stableCodePath) { this.skip(); } diff --git a/test/smoke/src/main.ts b/test/smoke/src/main.ts index 81661879ba6..f4231463a88 100644 --- a/test/smoke/src/main.ts +++ b/test/smoke/src/main.ts @@ -11,6 +11,8 @@ import * as tmp from 'tmp'; import * as rimraf from 'rimraf'; import * as mkdirp from 'mkdirp'; import { ncp } from 'ncp'; +import * as vscodetest from 'vscode-test'; +import fetch from 'node-fetch'; import { Application, Quality, @@ -85,6 +87,12 @@ function fail(errorMessage): void { const repoPath = path.join(__dirname, '..', '..', '..'); let quality: Quality; +let version: string | undefined; + +function parseVersion(version: string): { major: number, minor: number, patch: number } { + const [, major, minor, patch] = /^(\d+)\.(\d+)\.(\d+)/.exec(version)!; + return { major: parseInt(major), minor: parseInt(minor), patch: parseInt(patch) }; +} // // #### Electron Smoke Tests #### @@ -124,17 +132,21 @@ if (!opts.web) { } } + function getBuildVersion(root: string): string { + switch (process.platform) { + case 'darwin': + return require(path.join(root, 'Contents', 'Resources', 'app', 'package.json')).version; + default: + return require(path.join(root, 'resources', 'app', 'package.json')).version; + } + } + let testCodePath = opts.build; - let stableCodePath = opts['stable-build']; let electronPath: string; - let stablePath: string | undefined = undefined; if (testCodePath) { electronPath = getBuildElectronPath(testCodePath); - - if (stableCodePath) { - stablePath = getBuildElectronPath(stableCodePath); - } + version = getBuildVersion(testCodePath); } else { testCodePath = getDevElectronPath(); electronPath = testCodePath; @@ -147,10 +159,6 @@ if (!opts.web) { fail(`Can't find VSCode at ${electronPath}.`); } - if (typeof stablePath === 'string' && !fs.existsSync(stablePath)) { - fail(`Can't find Stable VSCode at ${stablePath}.`); - } - if (process.env.VSCODE_DEV === '1') { quality = Quality.Dev; } else if (electronPath.indexOf('Code - Insiders') >= 0 /* macOS/Windows */ || electronPath.indexOf('code-insiders') /* Linux */ >= 0) { @@ -222,10 +230,55 @@ async function setupRepository(): Promise { } } +async function ensureStableCode(): Promise { + if (opts.web || !opts['build']) { + return; + } + + let stableCodePath = opts['stable-build']; + if (!stableCodePath) { + const { major, minor } = parseVersion(version!); + const majorMinorVersion = `${major}.${minor - 1}`; + const versionsReq = await fetch('https://update.code.visualstudio.com/api/releases/stable', { headers: { 'x-api-version': '2' } }); + + if (!versionsReq.ok) { + throw new Error('Could not fetch releases from update server'); + } + + const versions: { version: string }[] = await versionsReq.json(); + const prefix = `${majorMinorVersion}.`; + const previousVersion = versions.find(v => v.version.startsWith(prefix)); + + if (!previousVersion) { + throw new Error(`Could not find suitable stable version ${majorMinorVersion}`); + } + + console.log(`*** Found VS Code v${version}, downloading previous VS Code version ${previousVersion.version}...`); + + const stableCodeExecutable = await vscodetest.downloadAndUnzipVSCode(previousVersion.version); + if (process.platform === 'darwin') { + // Visual Studio Code.app/Contents/MacOS/Electron + stableCodePath = path.dirname(path.dirname(path.dirname(stableCodeExecutable))); + } else { + // VSCode/Code.exe (Windows) | VSCode/code (Linux) + stableCodePath = path.dirname(stableCodeExecutable); + } + } + + if (!fs.existsSync(stableCodePath)) { + throw new Error(`Can't find Stable VSCode at ${stableCodePath}.`); + } + + console.log(`*** Using stable build ${stableCodePath} for migration tests`); + + opts['stable-build'] = stableCodePath; +} + async function setup(): Promise { console.log('*** Test data:', testDataPath); console.log('*** Preparing smoketest setup...'); + await ensureStableCode(); await setupRepository(); console.log('*** Smoketest setup done!\n'); @@ -304,9 +357,9 @@ describe(`VSCode Smoke Tests (${opts.web ? 'Web' : 'Electron'})`, () => { }); } - if (!opts.web && opts['stable-build']) { - describe(`Stable vs Insiders Smoke Tests: This test MUST run before releasing by providing the --stable-build command line argument`, () => { - setupDataMigrationTests(opts['stable-build'], testDataPath); + if (!opts.web && opts['build'] && !opts['remote']) { + describe(`Stable vs Insiders Smoke Tests: This test MUST run before releasing`, () => { + setupDataMigrationTests(opts, testDataPath); }); } diff --git a/test/smoke/yarn.lock b/test/smoke/yarn.lock index 02f622e50c7..bf51989f035 100644 --- a/test/smoke/yarn.lock +++ b/test/smoke/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@tootallnate/once@1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" + integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== + "@types/events@*": version "3.0.0" resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" @@ -45,6 +50,14 @@ dependencies: "@types/node" "*" +"@types/node-fetch@^2.5.10": + version "2.5.10" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.10.tgz#9b4d4a0425562f9fcea70b12cb3fcdd946ca8132" + integrity sha512-IpkX0AasN44hgEad0gEF/V6EgR5n69VEqPEgnmoM8GsIGro3PowbWs4tR6IhxUTyPLpOn+fiGG6nrQhcmoCuIQ== + dependencies: + "@types/node" "*" + form-data "^3.0.0" + "@types/node@*": version "13.11.0" resolved "https://registry.yarnpkg.com/@types/node/-/node-13.11.0.tgz#390ea202539c61c8fa6ba4428b57e05bc36dc47b" @@ -63,6 +76,13 @@ "@types/glob" "*" "@types/node" "*" +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" @@ -70,16 +90,39 @@ ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + 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= +big-integer@^1.6.17: + version "1.6.48" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.48.tgz#8fd88bd1632cba4a1c8c3e3d7159f08bb95b4b9e" + integrity sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w== + +binary@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/binary/-/binary-0.3.0.tgz#9f60553bc5ce8c3386f3b553cff47462adecaa79" + integrity sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk= + dependencies: + buffers "~0.1.1" + chainsaw "~0.1.0" + bluebird@^2.9.34: version "2.11.0" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1" integrity sha1-U0uQM8AiyVecVro7Plpcqvu2UOE= +bluebird@~3.4.1: + version "3.4.7" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3" + integrity sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM= + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -88,6 +131,16 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +buffer-indexof-polyfill@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz#d2732135c5999c64b277fcf9b1abe3498254729c" + integrity sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A== + +buffers@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb" + integrity sha1-skV5w77U1tOWru5tmorn9Ugqt7s= + call-bind@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.0.tgz#24127054bb3f9bdcb4b1fb82418186072f77b8ce" @@ -96,6 +149,13 @@ call-bind@^1.0.0: function-bind "^1.1.1" get-intrinsic "^1.0.0" +chainsaw@~0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/chainsaw/-/chainsaw-0.1.0.tgz#5eab50b28afe58074d0d58291388828b5e5fbc98" + integrity sha1-XqtQsor+WAdNDVgpE4iCi15fvJg= + dependencies: + traverse ">=0.3.0 <0.4" + chalk@^2.4.1: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -117,6 +177,13 @@ color-name@1.1.3: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= +combined-stream@^1.0.8: + 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.8.1: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" @@ -127,6 +194,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.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -138,6 +210,13 @@ cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" +debug@4: + version "4.3.2" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" + integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== + dependencies: + ms "2.1.2" + debug@^2.2.0: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -152,6 +231,11 @@ define-properties@^1.1.3: dependencies: object-keys "^1.0.12" +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= + dom-serializer@0: version "0.2.2" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" @@ -185,6 +269,13 @@ domutils@^1.5.1: dom-serializer "0" domelementtype "1" +duplexer2@~0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" + integrity sha1-ixLauHjA1p4+eJEFFmKjL8a93ME= + dependencies: + readable-stream "^2.0.2" + entities@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" @@ -241,11 +332,30 @@ exec-sh@^0.2.0: dependencies: merge "^1.2.0" +form-data@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" + integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + 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= +fstream@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045" + integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg== + dependencies: + graceful-fs "^4.1.2" + inherits "~2.0.0" + mkdirp ">=0.5 0" + rimraf "2" + function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -277,6 +387,11 @@ graceful-fs@^4.1.2: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== +graceful-fs@^4.2.2: + version "4.2.6" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" + integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== + has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" @@ -311,6 +426,23 @@ htmlparser2@^3.9.2: inherits "^2.0.1" readable-stream "^3.1.1" +http-proxy-agent@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" + integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== + dependencies: + "@tootallnate/once" "1" + agent-base "6" + debug "4" + +https-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" + integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== + dependencies: + agent-base "6" + debug "4" + inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -319,7 +451,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.1, inherits@^2.0.3: +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -370,6 +502,11 @@ is-symbol@^1.0.2: dependencies: has-symbols "^1.0.1" +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" @@ -380,6 +517,11 @@ json-parse-better-errors@^1.0.1: resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== +listenercount@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/listenercount/-/listenercount-1.0.1.tgz#84c8a72ab59c4725321480c975e6508342e70937" + integrity sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc= + load-json-file@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" @@ -400,6 +542,18 @@ merge@^1.2.0: resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.1.tgz#38bebf80c3220a8a487b6fcfb3941bb11720c145" integrity sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ== +mime-db@1.48.0: + version "1.48.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.48.0.tgz#e35b31045dd7eada3aaad537ed88a33afbef2d1d" + integrity sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ== + +mime-types@^2.1.12: + version "2.1.31" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.31.tgz#a00d76b74317c61f9c2db2218b8e9f8e9c5c9e6b" + integrity sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg== + dependencies: + mime-db "1.48.0" + minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" @@ -407,11 +561,18 @@ minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" -minimist@^1.2.0: +minimist@^1.2.0, minimist@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== +"mkdirp@>=0.5 0": + version "0.5.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" + integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== + dependencies: + minimist "^1.2.5" + mkdirp@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" @@ -422,6 +583,11 @@ ms@2.0.0: resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + ncp@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3" @@ -432,6 +598,11 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== +node-fetch@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" + integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== + normalize-package-data@^2.3.2: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" @@ -538,6 +709,11 @@ portastic@^1.0.1: commander "^2.8.1" debug "^2.2.0" +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + read-pkg@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" @@ -547,6 +723,19 @@ read-pkg@^3.0.0: normalize-package-data "^2.3.2" path-type "^3.0.0" +readable-stream@^2.0.2, readable-stream@~2.3.6: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + readable-stream@^3.1.1: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" @@ -564,13 +753,25 @@ resolve@^1.10.0: is-core-module "^2.1.0" path-parse "^1.0.6" -rimraf@^2.6.1: +rimraf@2, rimraf@^2.6.1: 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" +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" + +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + safe-buffer@~5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" @@ -581,6 +782,11 @@ safe-buffer@~5.2.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== +setimmediate@~1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= + shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" @@ -656,6 +862,13 @@ string_decoder@^1.1.1: dependencies: safe-buffer "~5.2.0" +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + strip-bom@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" @@ -680,12 +893,33 @@ tmp@0.0.33: dependencies: os-tmpdir "~1.0.2" +"traverse@>=0.3.0 <0.4": + version "0.3.9" + resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" + integrity sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk= + typescript@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.2.tgz#399ab18aac45802d6f2498de5054fcbbe716a805" integrity sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw== -util-deprecate@^1.0.1: +unzipper@^0.10.11: + version "0.10.11" + resolved "https://registry.yarnpkg.com/unzipper/-/unzipper-0.10.11.tgz#0b4991446472cbdb92ee7403909f26c2419c782e" + integrity sha512-+BrAq2oFqWod5IESRjL3S8baohbevGcVA+teAIOYWM3pDVdseogqbzhhvvmiyQrUNKFUnDMtELW3X8ykbyDCJw== + dependencies: + big-integer "^1.6.17" + binary "~0.3.0" + bluebird "~3.4.1" + buffer-indexof-polyfill "~1.0.0" + duplexer2 "~0.1.4" + fstream "^1.0.12" + graceful-fs "^4.2.2" + listenercount "~1.0.1" + readable-stream "~2.3.6" + setimmediate "~1.0.4" + +util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= @@ -698,6 +932,16 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" +vscode-test@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/vscode-test/-/vscode-test-1.5.2.tgz#d9ec3cab1815afae1d7d81923e3c685d13d32303" + integrity sha512-x9PVfKxF6EInH9iSFGQi0V8H5zIW1fC7RAer6yNQR6sy3WyOwlWkuT3I+wf75xW/cO53hxMi1aj/EvqQfDFOAg== + dependencies: + http-proxy-agent "^4.0.1" + https-proxy-agent "^5.0.0" + rimraf "^3.0.2" + unzipper "^0.10.11" + watch@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/watch/-/watch-1.0.2.tgz#340a717bde765726fa0aa07d721e0147a551df0c" From 05fd05dc00b79dacab4074097a21b98caf3d61ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Mon, 12 Jul 2021 05:56:27 -0700 Subject: [PATCH 47/66] remove `--stable-build` --- test/smoke/README.md | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/test/smoke/README.md b/test/smoke/README.md index ea0c5fd968b..91001acf828 100644 --- a/test/smoke/README.md +++ b/test/smoke/README.md @@ -21,8 +21,8 @@ yarn smoketest yarn smoketest --web --browser [chromium|webkit] # Build (Electron) -yarn smoketest --build --stable-build -example: yarn smoketest --build /Applications/Visual\ Studio\ Code\ -\ Insiders.app --stable-build /Applications/Visual\ Studio\ Code.app/ +yarn smoketest --build +example: yarn smoketest --build /Applications/Visual\ Studio\ Code\ -\ Insiders.app # Build (Web - read instructions below) yarn smoketest --build --web --browser [chromium|webkit] @@ -44,17 +44,6 @@ yarn && yarn compile yarn --cwd test/smoke ``` -#### Electron with --build and --stable-build - -In addition to the vscode repository, you will need the latest build and the previous stable build, so that the smoketest can test data migration. - -The recommended way to make these builds available for the smoketest is by downloading their archive versions (\*.zip) from the **[builds page](https://builds.code.visualstudio.com/)**, and extracting -them into two folders (e.g. with 'Extract All' on Windows). Pass the **absolute paths** of those folders to the smoketest as follows: - -```bash -yarn smoketest --build --stable-build -``` - #### Web There is no support for testing an old version to a new one yet. From ff9ee2c832eda9f96bca0fd613a96ff7c55ed091 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Mon, 12 Jul 2021 16:05:50 +0200 Subject: [PATCH 48/66] Handle already running compound background task in prelaunchTask --- .../tasks/browser/terminalTaskSystem.ts | 95 ++++++++++++------- 1 file changed, 61 insertions(+), 34 deletions(-) diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index 5c6d26d0384..f4b084de4a0 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -62,6 +62,7 @@ interface ActiveTerminalData { terminal: ITerminalInstance; task: Task; promise: Promise; + state?: TaskEventKind; } class InstanceManager { @@ -409,6 +410,16 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { this.removeInstances(task); } + private fireTaskEvent(event: TaskEvent) { + if (event.__task) { + const activeTask = this.activeTasks[event.__task.getMapKey()]; + if (activeTask) { + activeTask.state = event.kind; + } + } + this._onDidStateChange.fire(event); + } + public terminate(task: Task): Promise { let activeTerminal = this.activeTasks[task.getMapKey()]; if (!activeTerminal) { @@ -421,7 +432,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { let task = activeTerminal.task; try { onExit.dispose(); - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Terminated, task)); + this.fireTaskEvent(TaskEvent.create(TaskEventKind.Terminated, task)); } catch (error) { // Do nothing. } @@ -441,7 +452,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { let task = terminalData.task; try { onExit.dispose(); - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Terminated, task)); + this.fireTaskEvent(TaskEvent.create(TaskEventKind.Terminated, task)); } catch (error) { // Do nothing. } @@ -476,9 +487,9 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { let dependencyTask = await resolver.resolve(dependency.uri, dependency.task!); if (dependencyTask) { let key = dependencyTask.getMapKey(); - let promise = this.activeTasks[key] ? this.activeTasks[key].promise : undefined; + let promise = this.activeTasks[key] ? this.getDependencyPromise(this.activeTasks[key]) : undefined; if (!promise) { - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.DependsOnStarted, task)); + this.fireTaskEvent(TaskEvent.create(TaskEventKind.DependsOnStarted, task)); encounteredDependencies.add(task.getCommonTaskId()); promise = this.executeDependencyTask(dependencyTask, resolver, trigger, encounteredDependencies, alreadyResolved); } @@ -532,6 +543,30 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { } } + private createInactiveDependencyPromise(task: Task): Promise { + return new Promise(resolve => { + const taskInactiveDisposable = this.onDidStateChange(taskEvent => { + if ((taskEvent.kind === TaskEventKind.Inactive) && (taskEvent.__task === task)) { + taskInactiveDisposable.dispose(); + resolve({ exitCode: 0 }); + } + }); + }); + } + + private async getDependencyPromise(task: ActiveTerminalData): Promise { + if (!task.task.configurationProperties.isBackground) { + return task.promise; + } + if (!task.task.configurationProperties.problemMatchers || task.task.configurationProperties.problemMatchers.length === 0) { + return task.promise; + } + if (task.state === TaskEventKind.Inactive) { + return { exitCode: 0 }; + } + return this.createInactiveDependencyPromise(task.task); + } + private async executeDependencyTask(task: Task, resolver: ITaskResolver, trigger: string, encounteredDependencies: Set, alreadyResolved?: Map): Promise { // If the task is a background task with a watching problem matcher, we don't wait for the whole task to finish, // just for the problem matcher to go inactive. @@ -539,14 +574,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { return this.executeTask(task, resolver, trigger, encounteredDependencies, alreadyResolved); } - const inactivePromise = new Promise(resolve => { - const taskInactiveDisposable = this._onDidStateChange.event(taskEvent => { - if ((taskEvent.kind === TaskEventKind.Inactive) && (taskEvent.__task === task)) { - taskInactiveDisposable.dispose(); - resolve({ exitCode: 0 }); - } - }); - }); + const inactivePromise = this.createInactiveDependencyPromise(task); return Promise.race([inactivePromise, this.executeTask(task, resolver, trigger, encounteredDependencies, alreadyResolved)]); } @@ -584,7 +612,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { private async acquireInput(taskSystemInfo: TaskSystemInfo | undefined, workspaceFolder: IWorkspaceFolder | undefined, task: CustomTask | ContributedTask, variables: Set, alreadyResolved: Map): Promise { const resolved = await this.resolveVariablesFromSet(taskSystemInfo, workspaceFolder, task, variables, alreadyResolved); - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.AcquiredInput, task)); + this.fireTaskEvent(TaskEvent.create(TaskEventKind.AcquiredInput, task)); return resolved; } @@ -689,7 +717,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { return this.executeInTerminal(task, trigger, new VariableResolver(workspaceFolder, systemInfo, resolvedVariables.variables, this.configurationResolverService), workspaceFolder); } else { // Allows the taskExecutions array to be updated in the extension host - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.End, task)); + this.fireTaskEvent(TaskEvent.create(TaskEventKind.End, task)); return Promise.resolve({ exitCode: 0 }); } }, reason => { @@ -723,7 +751,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { return this.acquireInput(lastTask.getVerifiedTask().systemInfo, lastTask.getVerifiedTask().workspaceFolder, task, variables, alreadyResolved).then((resolvedVariables) => { if (!resolvedVariables) { // Allows the taskExecutions array to be updated in the extension host - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.End, task)); + this.fireTaskEvent(TaskEvent.create(TaskEventKind.End, task)); return { exitCode: 0 }; } this.currentTask.resolvedVariables = resolvedVariables; @@ -756,13 +784,13 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { if (event.kind === ProblemCollectorEventKind.BackgroundProcessingBegins) { eventCounter++; this.busyTasks[mapKey] = task; - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Active, task)); + this.fireTaskEvent(TaskEvent.create(TaskEventKind.Active, task)); } else if (event.kind === ProblemCollectorEventKind.BackgroundProcessingEnds) { eventCounter--; if (this.busyTasks[mapKey]) { delete this.busyTasks[mapKey]; } - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Inactive, task)); + this.fireTaskEvent(TaskEvent.create(TaskEventKind.Inactive, task)); if (eventCounter === 0) { if ((watchingProblemMatcher.numberOfMatches > 0) && watchingProblemMatcher.maxMarkerSeverity && (watchingProblemMatcher.maxMarkerSeverity >= MarkerSeverity.Error)) { @@ -793,13 +821,13 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { let processStartedSignaled = false; terminal.processReady.then(() => { if (!processStartedSignaled) { - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessStarted, task, terminal!.processId!)); + this.fireTaskEvent(TaskEvent.create(TaskEventKind.ProcessStarted, task, terminal!.processId!)); processStartedSignaled = true; } }, (_error) => { this.logService.error('Task terminal process never got ready'); }); - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Start, task, terminal.instanceId)); + this.fireTaskEvent(TaskEvent.create(TaskEventKind.Start, task, terminal.instanceId)); let skipLine: boolean = (!!task.command.presentation && task.command.presentation.echo); const onData = terminal.onLineData((line) => { if (skipLine) { @@ -824,7 +852,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { delete this.busyTasks[mapKey]; } this.removeFromActiveTasks(task); - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Changed)); + this.fireTaskEvent(TaskEvent.create(TaskEventKind.Changed)); if (exitCode !== undefined) { // Only keep a reference to the terminal if it is not being disposed. switch (task.command.presentation!.panel) { @@ -850,18 +878,17 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { watchingProblemMatcher.done(); watchingProblemMatcher.dispose(); if (!processStartedSignaled) { - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessStarted, task, terminal!.processId!)); + this.fireTaskEvent(TaskEvent.create(TaskEventKind.ProcessStarted, task, terminal!.processId!)); processStartedSignaled = true; } - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessEnded, task, exitCode)); + this.fireTaskEvent(TaskEvent.create(TaskEventKind.ProcessEnded, task, exitCode)); for (let i = 0; i < eventCounter; i++) { - let event = TaskEvent.create(TaskEventKind.Inactive, task); - this._onDidStateChange.fire(event); + this.fireTaskEvent(TaskEvent.create(TaskEventKind.Inactive, task)); } eventCounter = 0; - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.End, task)); + this.fireTaskEvent(TaskEvent.create(TaskEventKind.End, task)); toDispose.dispose(); resolve({ exitCode }); }); @@ -879,16 +906,16 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { let processStartedSignaled = false; terminal.processReady.then(() => { if (!processStartedSignaled) { - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessStarted, task, terminal!.processId!)); + this.fireTaskEvent(TaskEvent.create(TaskEventKind.ProcessStarted, task, terminal!.processId!)); processStartedSignaled = true; } }, (_error) => { // The process never got ready. Need to think how to handle this. }); - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Start, task, terminal.instanceId, resolver.values)); + this.fireTaskEvent(TaskEvent.create(TaskEventKind.Start, task, terminal.instanceId, resolver.values)); const mapKey = task.getMapKey(); this.busyTasks[mapKey] = task; - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Active, task)); + this.fireTaskEvent(TaskEvent.create(TaskEventKind.Active, task)); let problemMatchers = await this.resolveMatchers(resolver, task.configurationProperties.problemMatchers); let startStopProblemMatcher = new StartStopProblemCollector(problemMatchers, this.markerService, this.modelService, ProblemHandlingStrategy.Clean, this.fileService); this.terminalStatusManager.addTerminal(task, terminal, startStopProblemMatcher); @@ -905,7 +932,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { onExit.dispose(); let key = task.getMapKey(); this.removeFromActiveTasks(task); - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Changed)); + this.fireTaskEvent(TaskEvent.create(TaskEventKind.Changed)); if (exitCode !== undefined) { // Only keep a reference to the terminal if it is not being disposed. switch (task.command.presentation!.panel) { @@ -939,16 +966,16 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { startStopProblemMatcher.dispose(); }, 100); if (!processStartedSignaled && terminal) { - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessStarted, task, terminal.processId!)); + this.fireTaskEvent(TaskEvent.create(TaskEventKind.ProcessStarted, task, terminal.processId!)); processStartedSignaled = true; } - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessEnded, task, exitCode)); + this.fireTaskEvent(TaskEvent.create(TaskEventKind.ProcessEnded, task, exitCode)); if (this.busyTasks[mapKey]) { delete this.busyTasks[mapKey]; } - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Inactive, task)); - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.End, task)); + this.fireTaskEvent(TaskEvent.create(TaskEventKind.Inactive, task)); + this.fireTaskEvent(TaskEvent.create(TaskEventKind.End, task)); resolve({ exitCode }); }); }); @@ -962,7 +989,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { this.terminalGroupService.showPanel(task.command.presentation.focus); } this.activeTasks[task.getMapKey()] = { terminal, task, promise }; - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Changed)); + this.fireTaskEvent(TaskEvent.create(TaskEventKind.Changed)); return promise.then((summary) => { try { let telemetryEvent: TelemetryEvent = { From cf3ee0e54c3bf7e2a60535dcc0da1a071cbf40e5 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Mon, 12 Jul 2021 10:14:57 -0400 Subject: [PATCH 49/66] Preferences > Telemtry --- .../telemetry/common/telemetryService.ts | 2 +- .../browser/preferences.contribution.ts | 23 +++++++++++++++++++ .../preferences/browser/settingsEditor2.ts | 1 + .../electron-sandbox/desktop.contribution.ts | 2 +- 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/telemetry/common/telemetryService.ts b/src/vs/platform/telemetry/common/telemetryService.ts index d2170c42c66..a1e68d17aad 100644 --- a/src/vs/platform/telemetry/common/telemetryService.ts +++ b/src/vs/platform/telemetry/common/telemetryService.ts @@ -225,7 +225,7 @@ Registry.as(Extensions.Configuration).registerConfigurat 'default': true, 'restricted': true, 'scope': ConfigurationScope.APPLICATION, - 'tags': ['usesOnlineServices'] + 'tags': ['usesOnlineServices', 'telemetry'] } } }); diff --git a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts index 2faa0e20875..248c22d0acb 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts @@ -59,6 +59,7 @@ const SETTINGS_EDITOR_COMMAND_FOCUS_UP = 'settings.action.focusLevelUp'; const SETTINGS_EDITOR_COMMAND_SWITCH_TO_JSON = 'settings.switchToJSON'; const SETTINGS_EDITOR_COMMAND_FILTER_MODIFIED = 'settings.filterByModified'; const SETTINGS_EDITOR_COMMAND_FILTER_ONLINE = 'settings.filterByOnline'; +const SETTINGS_EDITOR_COMMAND_FILTER_TELEMETRY = 'settings.filterByTelemetry'; const SETTINGS_EDITOR_COMMAND_FILTER_UNTRUSTED = 'settings.filterUntrusted'; const SETTINGS_COMMAND_OPEN_SETTINGS = 'workbench.action.openSettings'; @@ -476,6 +477,28 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon order: 2 }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: SETTINGS_EDITOR_COMMAND_FILTER_TELEMETRY, + title: { value: nls.localize('showTelemtrySettings', "Telemetry Settings"), original: 'Telemetry Settings' }, + menu: { + id: MenuId.MenubarPreferencesMenu, + group: '1_settings', + order: 3, + } + }); + } + run(accessor: ServicesAccessor) { + const editorPane = accessor.get(IEditorService).activeEditorPane; + if (editorPane instanceof SettingsEditor2) { + editorPane.focusSearch('@tag:telemetry'); + } else { + accessor.get(IPreferencesService).openSettings(false, '@tag:telemetry'); + } + } + }); + registerAction2(class extends Action2 { constructor() { super({ diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index 460f5c75aa5..8f0618bab1a 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -97,6 +97,7 @@ export class SettingsEditor2 extends EditorPane { `@tag:${WORKSPACE_TRUST_SETTING_TAG}`, '@tag:sync', '@tag:usesOnlineServices', + '@tag:telemetry', `@${ID_SETTING_TAG}`, `@${EXTENSION_SETTING_TAG}`, `@${FEATURE_SETTING_TAG}scm`, diff --git a/src/vs/workbench/electron-sandbox/desktop.contribution.ts b/src/vs/workbench/electron-sandbox/desktop.contribution.ts index 59b1afc6709..2f06ca1b1ef 100644 --- a/src/vs/workbench/electron-sandbox/desktop.contribution.ts +++ b/src/vs/workbench/electron-sandbox/desktop.contribution.ts @@ -233,7 +233,7 @@ import { EditorsVisibleContext, SingleEditorGroupsContext } from 'vs/workbench/c 'type': 'boolean', 'description': localize('telemetry.enableCrashReporting', "Enable crash reports to be sent to a Microsoft online service. \nThis option requires restart to take effect."), 'default': true, - 'tags': ['usesOnlineServices'] + 'tags': ['usesOnlineServices', 'telemetry'] } } }); From c7e7eb20c496cd0d026ec88ba02b1cf953df7976 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 12 Jul 2021 16:41:19 +0200 Subject: [PATCH 50/66] log the url --- .../common/webExtensionsScannerService.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts b/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts index 25d1e8e05b8..3d4f6c1c623 100644 --- a/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts +++ b/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts @@ -405,13 +405,14 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten } private async toScannedExtension(webExtension: IWebExtension, isBuiltin: boolean): Promise { - const context = await this.requestService.request({ type: 'GET', url: this.toRequestUrl(joinPath(webExtension.location, 'package.json')) }, CancellationToken.None); + const url = this.toRequestUrl(joinPath(webExtension.location, 'package.json')); + const context = await this.requestService.request({ type: 'GET', url: url }, CancellationToken.None); if (!isSuccess(context)) { - throw new Error(`Error while fetching package.json for extension '${webExtension.identifier.id}'. Server returned ${context.res.statusCode}`); + throw new Error(`Error while fetching package.json for the extension '${webExtension.identifier.id}'. Server returned '${context.res.statusCode}' for the request '${url}'`); } const content = await asText(context); if (!content) { - throw new Error(`Error while fetching package.json for extension '${webExtension.identifier.id}'. Server returned no content`); + throw new Error(`Error while fetching package.json for extension '${webExtension.identifier.id}'. Server returned no content for the request '${url}'`); } let manifest: IExtensionManifest = JSON.parse(content); From 0824ebed7a6f578a33e6b6e9f9b527ecbb23c529 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Mon, 12 Jul 2021 16:48:33 +0200 Subject: [PATCH 51/66] fixes #128452 --- src/vs/platform/list/browser/listService.ts | 10 -------- .../contrib/list/browser/list.contribution.ts | 25 +++++++++++++++++++ src/vs/workbench/workbench.sandbox.main.ts | 3 +++ 3 files changed, 28 insertions(+), 10 deletions(-) create mode 100644 src/vs/workbench/contrib/list/browser/list.contribution.ts diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index dc99d2c80e3..10d47b82f00 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -110,10 +110,7 @@ export const WorkbenchListHasSelectionOrFocus = new RawContextKey('list export const WorkbenchListDoubleSelection = new RawContextKey('listDoubleSelection', false); export const WorkbenchListMultiSelection = new RawContextKey('listMultiSelection', false); export const WorkbenchListSelectionNavigation = new RawContextKey('listSelectionNavigation', false); -export const WorkbenchListSupportsKeyboardNavigation = new RawContextKey('listSupportsKeyboardNavigation', true); export const WorkbenchListAutomaticKeyboardNavigationKey = 'listAutomaticKeyboardNavigation'; -export const WorkbenchListAutomaticKeyboardNavigation = new RawContextKey(WorkbenchListAutomaticKeyboardNavigationKey, true); -export let didBindWorkbenchListAutomaticKeyboardNavigation = false; function createScopedContextKeyService(contextKeyService: IContextKeyService, widget: ListWidget): IContextKeyService { const result = contextKeyService.createScoped(widget.getHTMLElement()); @@ -1021,13 +1018,6 @@ function workbenchTreeDataPreamble boolean | undefined, disposable: IDisposable } { - WorkbenchListSupportsKeyboardNavigation.bindTo(contextKeyService); - - if (!didBindWorkbenchListAutomaticKeyboardNavigation) { - WorkbenchListAutomaticKeyboardNavigation.bindTo(contextKeyService); - didBindWorkbenchListAutomaticKeyboardNavigation = true; - } - const getAutomaticKeyboardNavigation = () => { // give priority to the context key value to disable this completely let automaticKeyboardNavigation = Boolean(contextKeyService.getContextKeyValue(WorkbenchListAutomaticKeyboardNavigationKey)); diff --git a/src/vs/workbench/contrib/list/browser/list.contribution.ts b/src/vs/workbench/contrib/list/browser/list.contribution.ts new file mode 100644 index 00000000000..f396bf16f84 --- /dev/null +++ b/src/vs/workbench/contrib/list/browser/list.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 { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { WorkbenchListAutomaticKeyboardNavigationKey } from 'vs/platform/list/browser/listService'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; + +export const WorkbenchListSupportsKeyboardNavigation = new RawContextKey('listSupportsKeyboardNavigation', true); +export const WorkbenchListAutomaticKeyboardNavigation = new RawContextKey(WorkbenchListAutomaticKeyboardNavigationKey, true); + +export class ListContext implements IWorkbenchContribution { + + constructor( + @IContextKeyService contextKeyService: IContextKeyService + ) { + WorkbenchListSupportsKeyboardNavigation.bindTo(contextKeyService); + WorkbenchListAutomaticKeyboardNavigation.bindTo(contextKeyService); + } +} + +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(ListContext, LifecyclePhase.Starting); diff --git a/src/vs/workbench/workbench.sandbox.main.ts b/src/vs/workbench/workbench.sandbox.main.ts index 54761a60ce9..3d9844c71a8 100644 --- a/src/vs/workbench/workbench.sandbox.main.ts +++ b/src/vs/workbench/workbench.sandbox.main.ts @@ -86,6 +86,9 @@ registerSingleton(IUserDataAutoSyncEnablementService, UserDataAutoSyncEnablement // Logs import 'vs/workbench/contrib/logs/electron-sandbox/logs.contribution'; +// List +import 'vs/workbench/contrib/list/browser/list.contribution'; + // Localizations import 'vs/workbench/contrib/localizations/browser/localizations.contribution'; From f09e72f5a7e310d554941775d51eae97b90f2885 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 12 Jul 2021 17:00:45 +0200 Subject: [PATCH 52/66] Move context keys into a workbench contribution --- .../browser/workspace.contribution.ts | 36 +++++++++++++++++-- .../workspaces/common/workspaceTrust.ts | 20 +---------- 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts b/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts index ec9f288f24a..ac0a88a6580 100644 --- a/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts +++ b/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts @@ -19,13 +19,13 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle import { Codicon } from 'vs/base/common/codicons'; import { ThemeColor } from 'vs/workbench/api/common/extHostTypes'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/common/statusbar'; import { IEditorPaneRegistry, EditorPaneDescriptor } from 'vs/workbench/browser/editor'; import { shieldIcon, WorkspaceTrustEditor } from 'vs/workbench/contrib/workspace/browser/workspaceTrustEditor'; import { WorkspaceTrustEditorInput } from 'vs/workbench/services/workspaces/browser/workspaceTrustEditorInput'; -import { WorkspaceTrustContext, WORKSPACE_TRUST_BANNER, WORKSPACE_TRUST_EMPTY_WINDOW, WORKSPACE_TRUST_ENABLED, WORKSPACE_TRUST_STARTUP_PROMPT, WORKSPACE_TRUST_UNTRUSTED_FILES } from 'vs/workbench/services/workspaces/common/workspaceTrust'; +import { WORKSPACE_TRUST_BANNER, WORKSPACE_TRUST_EMPTY_WINDOW, WORKSPACE_TRUST_ENABLED, WORKSPACE_TRUST_STARTUP_PROMPT, WORKSPACE_TRUST_UNTRUSTED_FILES } from 'vs/workbench/services/workspaces/common/workspaceTrust'; import { IEditorSerializer, IEditorFactoryRegistry, EditorExtensions } from 'vs/workbench/common/editor'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; @@ -55,6 +55,38 @@ const BANNER_RESTRICTED_MODE = 'workbench.banner.restrictedMode'; const STARTUP_PROMPT_SHOWN_KEY = 'workspace.trust.startupPrompt.shown'; const BANNER_RESTRICTED_MODE_DISMISSED_KEY = 'workbench.banner.restrictedMode.dismissed'; +/** + * Trust Context Keys + */ + +export const WorkspaceTrustContext = { + IsEnabled: new RawContextKey('isWorkspaceTrustEnabled', false, localize('workspaceTrustEnabledCtx', "Whether the workspace trust feature is enabled.")), + IsTrusted: new RawContextKey('isWorkspaceTrusted', false, localize('workspaceTrustedCtx', "Whether the current workspace has been trusted by the user.")) +}; + +export class WorkspaceTrustContextKeys extends Disposable implements IWorkbenchContribution { + + private readonly _ctxWorkspaceTrustEnabled: IContextKey; + private readonly _ctxWorkspaceTrustState: IContextKey; + + constructor( + @IContextKeyService contextKeyService: IContextKeyService, + @IWorkspaceTrustEnablementService workspaceTrustEnablementService: IWorkspaceTrustEnablementService, + @IWorkspaceTrustManagementService workspaceTrustManagementService: IWorkspaceTrustManagementService + ) { + super(); + + this._ctxWorkspaceTrustState = WorkspaceTrustContext.IsTrusted.bindTo(contextKeyService); + this._ctxWorkspaceTrustEnabled = WorkspaceTrustContext.IsEnabled.bindTo(contextKeyService); + this._ctxWorkspaceTrustEnabled.set(workspaceTrustEnablementService.isWorkspaceTrustEnabled()); + + this._register(workspaceTrustManagementService.onDidChangeTrust(trusted => this._ctxWorkspaceTrustState.set(trusted))); + } +} + +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(WorkspaceTrustContextKeys, LifecyclePhase.Restored); + + /* * Trust Request via Service UX handler */ diff --git a/src/vs/workbench/services/workspaces/common/workspaceTrust.ts b/src/vs/workbench/services/workspaces/common/workspaceTrust.ts index eb1b6f8fddb..fd54799207f 100644 --- a/src/vs/workbench/services/workspaces/common/workspaceTrust.ts +++ b/src/vs/workbench/services/workspaces/common/workspaceTrust.ts @@ -9,9 +9,7 @@ import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle' import { LinkedList } from 'vs/base/common/linkedList'; import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; -import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IRemoteAuthorityResolverService, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { getRemoteAuthority, isVirtualResource } from 'vs/platform/remote/common/remoteHosts'; @@ -31,11 +29,6 @@ export const WORKSPACE_TRUST_EMPTY_WINDOW = 'security.workspace.trust.emptyWindo export const WORKSPACE_TRUST_EXTENSION_SUPPORT = 'extensions.supportUntrustedWorkspaces'; export const WORKSPACE_TRUST_STORAGE_KEY = 'content.trust.model.key'; -export const WorkspaceTrustContext = { - IsEnabled: new RawContextKey('isWorkspaceTrustEnabled', false, localize('workspaceTrustEnabledCtx', "Whether the workspace trust feature is enabled.")), - IsTrusted: new RawContextKey('isWorkspaceTrusted', false, localize('workspaceTrustedCtx', "Whether the current workspace has been trusted by the user.")) -}; - export class CanonicalWorkspace implements IWorkspace { constructor( private readonly originalWorkspace: IWorkspace, @@ -633,24 +626,14 @@ export class WorkspaceTrustRequestService extends Disposable implements IWorkspa private readonly _onDidInitiateWorkspaceTrustRequest = this._register(new Emitter()); readonly onDidInitiateWorkspaceTrustRequest = this._onDidInitiateWorkspaceTrustRequest.event; - private readonly _ctxWorkspaceTrustEnabled: IContextKey; - private readonly _ctxWorkspaceTrustState: IContextKey; - constructor( - @IContextKeyService contextKeyService: IContextKeyService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IWorkspaceTrustEnablementService private readonly workspaceTrustEnablementService: IWorkspaceTrustEnablementService, @IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService ) { super(); - this._register(this.workspaceTrustManagementService.onDidChangeTrust(trusted => this.trusted = trusted)); - - this._ctxWorkspaceTrustEnabled = WorkspaceTrustContext.IsEnabled.bindTo(contextKeyService); - this._ctxWorkspaceTrustState = WorkspaceTrustContext.IsTrusted.bindTo(contextKeyService); - this._ctxWorkspaceTrustEnabled.set(this.workspaceTrustEnablementService.isWorkspaceTrustEnabled()); - this.trusted = this.workspaceTrustManagementService.isWorkspaceTrusted(); + this._register(this.workspaceTrustManagementService.onDidChangeTrust(trusted => this.trusted = trusted)); } private get trusted(): boolean { @@ -659,7 +642,6 @@ export class WorkspaceTrustRequestService extends Disposable implements IWorkspa private set trusted(trusted: boolean) { this._trusted = trusted; - this._ctxWorkspaceTrustState.set(trusted); } //#region Open file(s) trust request From c43f303015d885832ab6fff9d306b5d7f21358c9 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Mon, 12 Jul 2021 10:20:55 -0500 Subject: [PATCH 53/66] Revert "Fix#122454: Truncate the long terminal title (#122620)" (#128483) This reverts commit 821e1e577d25690e7119bb017c78d052fcde2f40. --- .../browser/parts/media/compositepart.css | 3 +- .../terminal/browser/media/terminal.css | 10 ------ .../contrib/terminal/browser/terminalIcon.ts | 15 +-------- .../contrib/terminal/browser/terminalView.ts | 33 ++++--------------- 4 files changed, 8 insertions(+), 53 deletions(-) diff --git a/src/vs/workbench/browser/parts/media/compositepart.css b/src/vs/workbench/browser/parts/media/compositepart.css index 700544a906a..fde7cdb706c 100644 --- a/src/vs/workbench/browser/parts/media/compositepart.css +++ b/src/vs/workbench/browser/parts/media/compositepart.css @@ -14,5 +14,4 @@ .monaco-workbench .part > .composite.title > .title-actions { flex: 1; padding-left: 5px; - overflow: hidden; -} +} \ No newline at end of file diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index 8f54a30d4f6..84224ba5141 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -374,13 +374,3 @@ .monaco-workbench .pane-body.integrated-terminal .terminal-group > .monaco-split-view2.vertical .terminal-drop-overlay.drop-after { top: 50%; } - -.monaco-workbench .terminal-single-tab .terminal-label-text { - overflow: hidden; - text-overflow: ellipsis; -} - -.monaco-workbench .terminal-single-tab { - min-width: 22px; - -} diff --git a/src/vs/workbench/contrib/terminal/browser/terminalIcon.ts b/src/vs/workbench/contrib/terminal/browser/terminalIcon.ts index 8b8356f9f52..a42658df404 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalIcon.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalIcon.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Codicon, CSSIcon } from 'vs/base/common/codicons'; +import { Codicon } from 'vs/base/common/codicons'; import { hash } from 'vs/base/common/hash'; import { URI } from 'vs/base/common/uri'; import { ColorScheme } from 'vs/platform/theme/common/theme'; @@ -55,16 +55,3 @@ export function getIconId(terminal: ITerminalInstance): string { } return terminal.icon.id; } - -const labelWithIconsRegex = new RegExp(`(\\\\)?\\$\\((${CSSIcon.iconNameExpression}(?:${CSSIcon.iconModifierExpression})?)\\)`, 'g'); -export function separateIconAndText(text: string): { icon: string, text: string } { - let match: RegExpMatchArray | null; - let icon = ''; - let textStart = 0; - while ((match = labelWithIconsRegex.exec(text)) !== null) { - textStart = (match.index || 0) + match[0].length; - const [, escaped, codicon] = match; - icon = escaped ? `$(${codicon})` : codicon; - } - return { icon, text: text.substring(textStart) }; -} diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts index a9f09c84c09..2a8c82a8ce9 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts @@ -33,7 +33,7 @@ import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { TerminalTabbedView } from 'vs/workbench/contrib/terminal/browser/terminalTabbedView'; import { Codicon } from 'vs/base/common/codicons'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; +import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { getColorForSeverity } from 'vs/workbench/contrib/terminal/browser/terminalStatusList'; import { createAndFillInContextMenuActions, MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { TerminalTabContextMenuGroup } from 'vs/workbench/contrib/terminal/browser/terminalMenus'; @@ -41,7 +41,7 @@ import { DropdownWithPrimaryActionViewItem } from 'vs/platform/actions/browser/d import { dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { ColorScheme } from 'vs/platform/theme/common/theme'; -import { getColorClass, getUriClasses, separateIconAndText } from 'vs/workbench/contrib/terminal/browser/terminalIcon'; +import { getColorClass, getUriClasses } from 'vs/workbench/contrib/terminal/browser/terminalIcon'; import { terminalStrings } from 'vs/workbench/contrib/terminal/common/terminalStrings'; import { withNullAsUndefined } from 'vs/base/common/types'; import { DataTransfers } from 'vs/base/browser/dnd'; @@ -407,7 +407,7 @@ class SingleTerminalTabActionViewItem extends MenuEntryActionViewItem { @ITerminalGroupService private readonly _terminalGroupService: ITerminalGroupService, @IThemeService private readonly _themeService: IThemeService, @IContextMenuService private readonly _contextMenuService: IContextMenuService, - @ICommandService private readonly _commandService: ICommandService + @ICommandService private readonly _commandService: ICommandService, ) { super(new MenuItemAction( { @@ -443,11 +443,6 @@ class SingleTerminalTabActionViewItem extends MenuEntryActionViewItem { this._register(toDisposable(() => dispose(this._elementDisposables))); } - override render(container: HTMLElement): void { - super.render(container); - container.classList.add('terminal-single-tab'); - } - override async onClick(event: MouseEvent): Promise { if (event.altKey && this._menuItemAction.alt) { this._commandService.executeCommand(this._menuItemAction.alt.id, { target: TerminalLocation.TerminalView } as ICreateTerminalOptions); @@ -505,23 +500,7 @@ class SingleTerminalTabActionViewItem extends MenuEntryActionViewItem { } } label.style.color = colorStyle; - - const elements = new Array(); - - const tabLabel = getSingleTabLabel(instance, ThemeIcon.isThemeIcon(this._commandAction.item.icon) ? this._commandAction.item.icon : undefined); - - // ensures that the icon will always be displayed and allows for truncation of text - // to prevent overflow into the actions - const iconAndText = separateIconAndText(tabLabel); - - if (iconAndText.icon) { - elements.push(renderIcon({ id: iconAndText.icon })); - const node = dom.$(`span`); - node.classList.add('terminal-label-text'); - node.innerText = iconAndText.text; - elements.push(node); - dom.reset(label, ...elements); - } + dom.reset(label, ...renderLabelWithIcons(getSingleTabLabel(instance, ThemeIcon.isThemeIcon(this._commandAction.item.icon) ? this._commandAction.item.icon : undefined))); if (this._altCommand) { label.classList.remove(this._altCommand); @@ -563,7 +542,7 @@ class SingleTerminalTabActionViewItem extends MenuEntryActionViewItem { } } -function getSingleTabLabel(instance: ITerminalInstance | undefined, icon?: ThemeIcon): string { +function getSingleTabLabel(instance: ITerminalInstance | undefined, icon?: ThemeIcon) { // Don't even show the icon if there is no title as the icon would shift around when the title // is added if (!instance || !instance.title) { @@ -634,7 +613,7 @@ class TerminalThemeIconStyle extends Themable { const iconClasses = getUriClasses(instance, colorTheme.type); if (uri instanceof URI && iconClasses && iconClasses.length > 1) { css += ( - `.monaco-workbench .${iconClasses[0]} .monaco-highlighted-label .codicon, .monaco-action-bar .terminal-uri-icon.single-terminal-tab.action-label:not(.alt-command) .codicon` + + `.monaco-workbench .${iconClasses[0]} .monaco-highlighted-label .codicon, .monaco-action-bar .terminal-uri-icon.single-terminal-tab.action-label:not(.alt-command) .codicon,` + `{background-image: ${dom.asCSSUrl(uri)};}` ); } From 93330684487313c3d7be2593117b2e4165cc1aac Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 12 Jul 2021 08:33:19 -0700 Subject: [PATCH 54/66] fix #128301 --- .../workbench/contrib/terminal/browser/terminalGroupService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalGroupService.ts b/src/vs/workbench/contrib/terminal/browser/terminalGroupService.ts index f33d1015823..9c046081f4e 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalGroupService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalGroupService.ts @@ -295,8 +295,8 @@ export class TerminalGroupService extends Disposable implements ITerminalGroupSe if (this.activeGroupIndex !== instanceLocation.groupIndex) { this.activeGroupIndex = instanceLocation.groupIndex; this._onDidChangeActiveGroup.fire(this.activeGroup); - instanceLocation.group.setActiveInstanceByIndex(activeInstanceIndex, true); } + instanceLocation.group.setActiveInstanceByIndex(activeInstanceIndex, true); this.groups.forEach((g, i) => g.setVisible(i === instanceLocation.groupIndex)); } From c1cfeed1a7ace2febb8a32144fa98d32c560e4bb Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 12 Jul 2021 08:42:03 -0700 Subject: [PATCH 55/66] fix #128044 --- src/vs/workbench/api/common/extHostTerminalService.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index 21d9cbdedbe..e36e5403ee1 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -845,9 +845,11 @@ function asTerminalIcon(iconPath?: vscode.Uri | { light: vscode.Uri; dark: vscod if (!iconPath) { return undefined; } - if (!('id' in iconPath)) { + + if (typeof iconPath === 'string' || !('id' in iconPath)) { return iconPath; } + return { id: iconPath.id, color: iconPath.color as ThemeColor From 526a7a28a577a8e3bcb7cb16fa5b373d52694cdf Mon Sep 17 00:00:00 2001 From: Josh Spicer Date: Mon, 12 Jul 2021 12:06:00 -0400 Subject: [PATCH 56/66] Allow readme to be displayed if defaultValue is set (#128240) --- .../workbench/contrib/welcome/page/browser/welcomePage.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts index 4e14ed6342c..3692a08c730 100644 --- a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts +++ b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts @@ -152,12 +152,14 @@ function isWelcomePageEnabled(configurationService: IConfigurationService, conte return welcomeEnabled.value; } } - if (startupEditor.value === 'readme' && startupEditor.userValue !== 'readme') { - console.error('Warning: `workbench.startupEditor: readme` setting ignored due to being set somewhere other than user settings'); + + if (startupEditor.value === 'readme' && (startupEditor.userValue !== 'readme' || startupEditor.defaultValue !== 'readme')) { + console.error(`Warning: 'workbench.startupEditor: readme' setting ignored due to being set somewhere other than user or default settings (user=${startupEditor.userValue}, default=${startupEditor.defaultValue})`); } return startupEditor.value === 'welcomePage' || startupEditor.value === 'legacy_welcomePage' || startupEditor.userValue === 'readme' + || startupEditor.defaultValue === 'readme' || (contextService.getWorkbenchState() === WorkbenchState.EMPTY && (startupEditor.value === 'legacy_welcomePageInEmptyWorkbench' || startupEditor.value === 'welcomePageInEmptyWorkbench')); } From d2c4418ec409f6723df6007f24bb13275c07a6c4 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Mon, 12 Jul 2021 09:14:21 -0700 Subject: [PATCH 57/66] Fixup boolean logic --- src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts index 3692a08c730..c8334328239 100644 --- a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts +++ b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts @@ -153,7 +153,7 @@ function isWelcomePageEnabled(configurationService: IConfigurationService, conte } } - if (startupEditor.value === 'readme' && (startupEditor.userValue !== 'readme' || startupEditor.defaultValue !== 'readme')) { + if (startupEditor.value === 'readme' && startupEditor.userValue !== 'readme' && startupEditor.defaultValue !== 'readme') { console.error(`Warning: 'workbench.startupEditor: readme' setting ignored due to being set somewhere other than user or default settings (user=${startupEditor.userValue}, default=${startupEditor.defaultValue})`); } return startupEditor.value === 'welcomePage' From 0aa83e5eb72aad3908c11a4496af8581bf442400 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 12 Jul 2021 18:19:00 +0200 Subject: [PATCH 58/66] Debt: do not store the trust state in the request service --- .../workspaces/common/workspaceTrust.ts | 23 ++++--------------- 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/services/workspaces/common/workspaceTrust.ts b/src/vs/workbench/services/workspaces/common/workspaceTrust.ts index fd54799207f..10c4c652c09 100644 --- a/src/vs/workbench/services/workspaces/common/workspaceTrust.ts +++ b/src/vs/workbench/services/workspaces/common/workspaceTrust.ts @@ -612,8 +612,6 @@ export class WorkspaceTrustManagementService extends Disposable implements IWork export class WorkspaceTrustRequestService extends Disposable implements IWorkspaceTrustRequestService { _serviceBrand: undefined; - private _trusted!: boolean; - private _openFilesTrustRequestPromise?: Promise; private _openFilesTrustRequestResolver?: (response: WorkspaceTrustUriResponse) => void; @@ -631,17 +629,6 @@ export class WorkspaceTrustRequestService extends Disposable implements IWorkspa @IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService ) { super(); - - this.trusted = this.workspaceTrustManagementService.isWorkspaceTrusted(); - this._register(this.workspaceTrustManagementService.onDidChangeTrust(trusted => this.trusted = trusted)); - } - - private get trusted(): boolean { - return this._trusted; - } - - private set trusted(trusted: boolean) { - this._trusted = trusted; } //#region Open file(s) trust request @@ -684,7 +671,7 @@ export class WorkspaceTrustRequestService extends Disposable implements IWorkspa async requestOpenFilesTrust(uris: URI[]): Promise { // If workspace is untrusted, there is no conflict - if (!this.trusted) { + if (!this.workspaceTrustManagementService.isWorkspaceTrusted()) { return WorkspaceTrustUriResponse.Open; } @@ -730,7 +717,7 @@ export class WorkspaceTrustRequestService extends Disposable implements IWorkspa private resolveWorkspaceTrustRequest(trusted?: boolean): void { if (this._workspaceTrustRequestResolver) { - this._workspaceTrustRequestResolver(trusted ?? this.trusted); + this._workspaceTrustRequestResolver(trusted ?? this.workspaceTrustManagementService.isWorkspaceTrusted()); this._workspaceTrustRequestResolver = undefined; this._workspaceTrustRequestPromise = undefined; @@ -747,7 +734,7 @@ export class WorkspaceTrustRequestService extends Disposable implements IWorkspa } async completeWorkspaceTrustRequest(trusted?: boolean): Promise { - if (trusted === undefined || trusted === this.trusted) { + if (trusted === undefined || trusted === this.workspaceTrustManagementService.isWorkspaceTrusted()) { this.resolveWorkspaceTrustRequest(trusted); return; } @@ -759,8 +746,8 @@ export class WorkspaceTrustRequestService extends Disposable implements IWorkspa async requestWorkspaceTrust(options?: WorkspaceTrustRequestOptions): Promise { // Trusted workspace - if (this.trusted) { - return this.trusted; + if (this.workspaceTrustManagementService.isWorkspaceTrusted()) { + return this.workspaceTrustManagementService.isWorkspaceTrusted(); } // Modal request From 49feb91c32e7df4ae6d8351b58702f8f3cfcdd99 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 12 Jul 2021 18:34:40 +0200 Subject: [PATCH 59/66] Resolve promise when workspace state changes (#128344) --- .../services/workspaces/common/workspaceTrust.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/services/workspaces/common/workspaceTrust.ts b/src/vs/workbench/services/workspaces/common/workspaceTrust.ts index 10c4c652c09..5c1ce7f5731 100644 --- a/src/vs/workbench/services/workspaces/common/workspaceTrust.ts +++ b/src/vs/workbench/services/workspaces/common/workspaceTrust.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Emitter } from 'vs/base/common/event'; +import { Emitter, Event } from 'vs/base/common/event'; import { splitName } from 'vs/base/common/labels'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { LinkedList } from 'vs/base/common/linkedList'; @@ -739,9 +739,11 @@ export class WorkspaceTrustRequestService extends Disposable implements IWorkspa return; } - // Update storage, transition workspace, and resolve the promise + // Register one-time event handler to resolve the promise when workspace trust changed + Event.once(this.workspaceTrustManagementService.onDidChangeTrust)(trusted => this.resolveWorkspaceTrustRequest(trusted)); + + // Update storage, transition workspace state await this.workspaceTrustManagementService.setWorkspaceTrust(trusted); - this.resolveWorkspaceTrustRequest(trusted); } async requestWorkspaceTrust(options?: WorkspaceTrustRequestOptions): Promise { From 4cf81a2a567be1585bdbf3464127183a08f00381 Mon Sep 17 00:00:00 2001 From: Alan Ren Date: Mon, 12 Jul 2021 09:38:23 -0700 Subject: [PATCH 60/66] make dropdown arrow larger to meet accessibility requirement (#127839) * make dropdown arrow bold * Update style.css --- src/vs/workbench/browser/media/style.css | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/browser/media/style.css b/src/vs/workbench/browser/media/style.css index cf008e43f65..1b9c8bfda9e 100644 --- a/src/vs/workbench/browser/media/style.css +++ b/src/vs/workbench/browser/media/style.css @@ -192,10 +192,10 @@ body.web { .monaco-workbench .select-container:after { content: "\eab4"; font-family: codicon; - font-size: 14px; - width: 14px; - height: 14px; - line-height: 14px; + font-size: 16px; + width: 16px; + height: 16px; + line-height: 16px; position: absolute; top: 0; bottom: 0; From c35c7e050daf8aabd06de6c30fe52de6f577537e Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Mon, 12 Jul 2021 10:06:40 -0700 Subject: [PATCH 61/66] move to using an id token for settings sync --- src/vs/editor/common/modes.ts | 1 + .../userDataSync/browser/userDataSyncWorkbenchService.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index d9863fff7fe..d5284c24f0d 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -1544,6 +1544,7 @@ export interface AuthenticationSession { id: string; } scopes: ReadonlyArray; + idToken?: string; } /** diff --git a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts index 120942e6f82..7796a8a328e 100644 --- a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts +++ b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts @@ -58,7 +58,7 @@ class UserDataSyncAccount implements IUserDataSyncAccount { get sessionId(): string { return this.session.id; } get accountName(): string { return this.session.account.label; } get accountId(): string { return this.session.account.id; } - get token(): string { return this.session.accessToken; } + get token(): string { return this.session.idToken || this.session.accessToken; } } export class UserDataSyncWorkbenchService extends Disposable implements IUserDataSyncWorkbenchService { From c1b1f56af1c9a3102b26491558ec9b702c4a0804 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 12 Jul 2021 19:16:22 +0200 Subject: [PATCH 62/66] update distro --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index e16e628a9db..8055c59769b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.59.0", - "distro": "e2cacb542803f4db7885aaa04ae90b834ac37db1", + "distro": "b10247fd6dfcf2e8ff7fb4772bc58a2e97e108a3", "author": { "name": "Microsoft Corporation" }, @@ -223,4 +223,4 @@ "elliptic": "^6.5.3", "nwmatcher": "^1.4.4" } -} +} \ No newline at end of file From 2e6df245d2fd7110351be74d9e060aae3db2eefc Mon Sep 17 00:00:00 2001 From: Chuck Lantz Date: Mon, 12 Jul 2021 10:52:05 -0700 Subject: [PATCH 63/66] Add workflow_dispatch --- .github/workflows/devcontainer-cache.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/devcontainer-cache.yml b/.github/workflows/devcontainer-cache.yml index 87041953080..4bb96145b02 100644 --- a/.github/workflows/devcontainer-cache.yml +++ b/.github/workflows/devcontainer-cache.yml @@ -1,6 +1,7 @@ name: VS Code Repo Dev Container Cache Image Generation on: + workflow_dispatch: push: # Currently doing this for main, but could be done for PRs as well branches: From c1ae18fad7924af3f4bf4171b8c269b52ce59e21 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Mon, 12 Jul 2021 14:48:27 -0400 Subject: [PATCH 64/66] Fix #26425 --- extensions/git/package.json | 4 ++-- extensions/git/src/repository.ts | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 55f83ea6a89..e8f969896db 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -577,7 +577,7 @@ }, { "command": "git.openChange", - "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && resourcePath in git.changedResources" }, { "command": "git.stage", @@ -1324,7 +1324,7 @@ { "command": "git.openChange", "group": "navigation", - "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && !isInDiffEditor && resourceScheme == file" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && !isInDiffEditor && resourceScheme == file && resourcePath in git.changedResources" }, { "command": "git.stageSelectedRanges", diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 37c0eb94db4..0f4220e3649 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -1847,6 +1847,7 @@ export class Repository implements Disposable { this._submodules = submodules!; this.rebaseCommit = rebaseCommit; + const untrackedChanges = scopedConfig.get<'mixed' | 'separate' | 'hidden'>('untrackedChanges'); const index: Resource[] = []; const workingTree: Resource[] = []; @@ -1905,6 +1906,9 @@ export class Repository implements Disposable { // set count badge this.setCountBadge(); + // Update context key with changed resources + commands.executeCommand('setContext', 'git.changedResources', workingTree.map(r => r.resourceUri.fsPath.toString())); + this._onDidChangeStatus.fire(); this._sourceControl.commitTemplate = await this.getInputTemplate(); From 115192a214f208fc7cda3a1567c7d05df6ec0c1c Mon Sep 17 00:00:00 2001 From: rebornix Date: Mon, 12 Jul 2021 18:07:05 -0700 Subject: [PATCH 65/66] move off sidebyside editor input. --- .../browser/interactiveEditorInput.ts | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts index f1792729f3a..ad2e11fc0ad 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Event } from 'vs/base/common/event'; import * as paths from 'vs/base/common/path'; import { isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; @@ -10,17 +11,17 @@ import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IEditorInput, IUntypedEditorInput } from 'vs/workbench/common/editor'; -import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput'; +import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { IInteractiveDocumentService } from 'vs/workbench/contrib/interactive/browser/interactiveDocumentService'; import { IResolvedNotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { ICompositeNotebookEditorInput, NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; -export class InteractiveEditorInput extends SideBySideEditorInput implements ICompositeNotebookEditorInput { +export class InteractiveEditorInput extends EditorInput implements ICompositeNotebookEditorInput { static create(instantiationService: IInstantiationService, resource: URI, inputResource: URI) { return instantiationService.createInstance(InteractiveEditorInput, resource, inputResource); } - static override readonly ID: string = 'workbench.input.interactive'; + static readonly ID: string = 'workbench.input.interactive'; override get typeId(): string { return InteractiveEditorInput.ID; @@ -56,6 +57,10 @@ export class InteractiveEditorInput extends SideBySideEditorInput implements ICo private _modelService: IModelService; private _interactiveDocumentService: IInteractiveDocumentService; + get primary(): EditorInput { + return this._notebookEditorInput; + } + constructor( resource: URI, @@ -65,7 +70,7 @@ export class InteractiveEditorInput extends SideBySideEditorInput implements ICo @IInteractiveDocumentService interactiveDocumentService: IInteractiveDocumentService ) { const input = NotebookEditorInput.create(instantiationService, resource, 'interactive', {}); - super(undefined, undefined, input, input); + super(); this._notebookEditorInput = input; this._register(this._notebookEditorInput); this._inputResource = inputResource; @@ -74,6 +79,25 @@ export class InteractiveEditorInput extends SideBySideEditorInput implements ICo this._inputModel = null; this._modelService = modelService; this._interactiveDocumentService = interactiveDocumentService; + + this._registerListeners(); + } + + private _registerListeners(): void { + const oncePrimaryDisposed = Event.once(this.primary.onWillDispose); + this._register(oncePrimaryDisposed(() => { + if (!this.isDisposed()) { + this.dispose(); + } + })); + + // Re-emit some events from the primary side to the outside + this._register(this.primary.onDidChangeDirty(() => this._onDidChangeDirty.fire())); + this._register(this.primary.onDidChangeLabel(() => this._onDidChangeLabel.fire())); + + // Re-emit some events from both sides to the outside + this._register(this.primary.onDidChangeCapabilities(() => this._onDidChangeCapabilities.fire())); + } override isDirty() { From f6df685c62da50886f7540cbf768ed4333d58bea Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Mon, 12 Jul 2021 18:07:21 -0700 Subject: [PATCH 66/66] Fix #128510 --- .../contrib/welcome/gettingStarted/browser/gettingStarted.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.css b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.css index 3b215f316a3..d50655a885e 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.css +++ b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.css @@ -232,9 +232,9 @@ font-size: 13px; box-sizing: border-box; line-height: normal; - margin: 8px 8px 12px; + margin: 8px 8px 12px 0; padding: 3px 6px 6px; - left: 1px; + left: -3px; text-align: left; }