From 334795d2da3849c7b6e8944e663f48ab40c23b56 Mon Sep 17 00:00:00 2001 From: Prashant Cholachagudda Date: Thu, 23 Jun 2022 16:58:30 +0530 Subject: [PATCH 001/150] Add VSCode session id to gallery extension query --- .../extensionManagement/common/extensionGalleryService.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index 6f04a76edb6..cfea8e58f80 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -923,12 +923,14 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi const commonHeaders = await this.commonHeadersPromise; const data = JSON.stringify(query.raw); + const { sessionId } = await this.telemetryService.getTelemetryInfo(); const headers = { ...commonHeaders, 'Content-Type': 'application/json', 'Accept': 'application/json;api-version=3.0-preview.1', 'Accept-Encoding': 'gzip', - 'Content-Length': String(data.length) + 'Content-Length': String(data.length), + 'VSCode-SessionId': sessionId }; const startTime = new Date().getTime(); From d9ccb7eff15945c823c72a7e8c96a1c5e5e0214f Mon Sep 17 00:00:00 2001 From: Prashant Cholachagudda Date: Mon, 27 Jun 2022 15:46:18 +0530 Subject: [PATCH 002/150] assign 'VSCode-SessionId' header in marketplace.ts --- .../common/extensionGalleryService.ts | 11 +++- .../common/extensionGalleryService.test.ts | 59 ++++++++++++++++++- .../externalServices/common/marketplace.ts | 13 +++- .../platform/windows/electron-main/window.ts | 9 ++- 4 files changed, 83 insertions(+), 9 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index cfea8e58f80..3515dea4464 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -584,7 +584,14 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi const config = productService.extensionsGallery; this.extensionsGalleryUrl = config && config.serviceUrl; this.extensionsControlUrl = config && config.controlUrl; - this.commonHeadersPromise = resolveMarketplaceHeaders(productService.version, productService, this.environmentService, this.configurationService, this.fileService, storageService); + this.commonHeadersPromise = resolveMarketplaceHeaders( + productService.version, + productService, + this.environmentService, + this.configurationService, + this.fileService, + storageService, + this.telemetryService); } private api(path = ''): string { @@ -923,14 +930,12 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi const commonHeaders = await this.commonHeadersPromise; const data = JSON.stringify(query.raw); - const { sessionId } = await this.telemetryService.getTelemetryInfo(); const headers = { ...commonHeaders, 'Content-Type': 'application/json', 'Accept': 'application/json;api-version=3.0-preview.1', 'Accept-Encoding': 'gzip', 'Content-Length': String(data.length), - 'VSCode-SessionId': sessionId }; const startTime = new Date().getTime(); diff --git a/src/vs/platform/extensionManagement/test/common/extensionGalleryService.test.ts b/src/vs/platform/extensionManagement/test/common/extensionGalleryService.test.ts index 4312de805f8..4db659c6fc8 100644 --- a/src/vs/platform/extensionManagement/test/common/extensionGalleryService.test.ts +++ b/src/vs/platform/extensionManagement/test/common/extensionGalleryService.test.ts @@ -21,8 +21,11 @@ import product from 'vs/platform/product/common/product'; import { IProductService } from 'vs/platform/product/common/productService'; import { resolveMarketplaceHeaders } from 'vs/platform/externalServices/common/marketplace'; import { InMemoryStorageService, IStorageService } from 'vs/platform/storage/common/storage'; -import { TelemetryConfiguration, TELEMETRY_SETTING_ID } from 'vs/platform/telemetry/common/telemetry'; +import { ITelemetryInfo, ITelemetryService, TelemetryConfiguration, TelemetryLevel, TELEMETRY_SETTING_ID } from 'vs/platform/telemetry/common/telemetry'; import { TargetPlatform } from 'vs/platform/extensions/common/extensions'; +import { ClassifiedEvent, GDPRClassification, StrictPropertyCheck } from 'vs/platform/telemetry/common/gdprTypings'; +import { staticObservableValue } from 'vs/base/common/observableValue'; +import { Emitter, Event } from 'vs/base/common/event'; class EnvironmentServiceMock extends mock() { override readonly serviceMachineIdResource: URI; @@ -36,6 +39,7 @@ class EnvironmentServiceMock extends mock() { suite('Extension Gallery Service', () => { const disposables: DisposableStore = new DisposableStore(); let fileService: IFileService, environmentService: IEnvironmentService, storageService: IStorageService, productService: IProductService, configurationService: IConfigurationService; + let telemetryService: ITelemetryService; setup(() => { const serviceMachineIdResource = joinPath(URI.file('tests').with({ scheme: 'vscode-tests' }), 'machineid'); @@ -47,14 +51,15 @@ suite('Extension Gallery Service', () => { configurationService = new TestConfigurationService({ [TELEMETRY_SETTING_ID]: TelemetryConfiguration.ON }); configurationService.updateValue(TELEMETRY_SETTING_ID, TelemetryConfiguration.ON); productService = { _serviceBrand: undefined, ...product, enableTelemetry: true }; + telemetryService = new MockTelemetryService(); }); teardown(() => disposables.clear()); test('marketplace machine id', async () => { - const headers = await resolveMarketplaceHeaders(product.version, productService, environmentService, configurationService, fileService, storageService); + const headers = await resolveMarketplaceHeaders(product.version, productService, environmentService, configurationService, fileService, storageService, telemetryService); assert.ok(isUUID(headers['X-Market-User-Id'])); - const headers2 = await resolveMarketplaceHeaders(product.version, productService, environmentService, configurationService, fileService, storageService); + const headers2 = await resolveMarketplaceHeaders(product.version, productService, environmentService, configurationService, fileService, storageService, telemetryService); assert.strictEqual(headers['X-Market-User-Id'], headers2['X-Market-User-Id']); }); @@ -153,3 +158,51 @@ suite('Extension Gallery Service', () => { return { version, targetPlatform } as IRawGalleryExtensionVersion; } }); + +class MockTelemetryService implements ITelemetryService { + public _serviceBrand: undefined; + public telemetryLevel = staticObservableValue(TelemetryLevel.USAGE); + public sendErrorTelemetry = true; + + public events: any[] = []; + + private readonly emitter = new Emitter(); + + public get eventLogged(): Event { + return this.emitter.event; + } + + public setEnabled(value: boolean): void { + } + + public setExperimentProperty(name: string, value: string): void { + } + + public publicLog(eventName: string, data?: any): Promise { + const event = { name: eventName, data: data }; + this.events.push(event); + this.emitter.fire(event); + return Promise.resolve(); + } + + public publicLog2 = never, T extends GDPRClassification = never>(eventName: string, data?: StrictPropertyCheck) { + return this.publicLog(eventName, data as any); + } + + public publicLogError(eventName: string, data?: any): Promise { + return this.publicLog(eventName, data); + } + + public publicLogError2 = never, T extends GDPRClassification = never>(eventName: string, data?: StrictPropertyCheck) { + return this.publicLogError(eventName, data as any); + } + + public getTelemetryInfo(): Promise { + return Promise.resolve({ + instanceId: 'someValue.instanceId', + sessionId: 'someValue.sessionId', + machineId: 'someValue.machineId', + firstSessionDate: 'someValue.firstSessionDate' + }); + } +} diff --git a/src/vs/platform/externalServices/common/marketplace.ts b/src/vs/platform/externalServices/common/marketplace.ts index 6d2c25ab86d..5923e1cd8e8 100644 --- a/src/vs/platform/externalServices/common/marketplace.ts +++ b/src/vs/platform/externalServices/common/marketplace.ts @@ -10,17 +10,26 @@ import { getServiceMachineId } from 'vs/platform/externalServices/common/service import { IFileService } from 'vs/platform/files/common/files'; import { IProductService } from 'vs/platform/product/common/productService'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; +import { ITelemetryService, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; import { getTelemetryLevel, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; -export async function resolveMarketplaceHeaders(version: string, productService: IProductService, environmentService: IEnvironmentService, configurationService: IConfigurationService, fileService: IFileService, storageService: IStorageService | undefined): Promise { +export async function resolveMarketplaceHeaders(version: string, + productService: IProductService, + environmentService: IEnvironmentService, + configurationService: IConfigurationService, + fileService: IFileService, + storageService: IStorageService | undefined, + telemetryService: ITelemetryService): Promise { const headers: IHeaders = { 'X-Market-Client-Id': `VSCode ${version}`, 'User-Agent': `VSCode ${version} (${productService.nameShort})` }; const uuid = await getServiceMachineId(environmentService, fileService, storageService); + const { sessionId } = await telemetryService.getTelemetryInfo(); + if (supportsTelemetry(productService, environmentService) && getTelemetryLevel(configurationService) === TelemetryLevel.USAGE) { headers['X-Market-User-Id'] = uuid; + headers['VSCode-SessionId'] = sessionId; } return headers; } diff --git a/src/vs/platform/windows/electron-main/window.ts b/src/vs/platform/windows/electron-main/window.ts index 02524b2235b..fcc8c93ee47 100644 --- a/src/vs/platform/windows/electron-main/window.ts +++ b/src/vs/platform/windows/electron-main/window.ts @@ -542,7 +542,14 @@ export class CodeWindow extends Disposable implements ICodeWindow { private marketplaceHeadersPromise: Promise | undefined; private getMarketplaceHeaders(): Promise { if (!this.marketplaceHeadersPromise) { - this.marketplaceHeadersPromise = resolveMarketplaceHeaders(this.productService.version, this.productService, this.environmentMainService, this.configurationService, this.fileService, this.applicationStorageMainService); + this.marketplaceHeadersPromise = resolveMarketplaceHeaders( + this.productService.version, + this.productService, + this.environmentMainService, + this.configurationService, + this.fileService, + this.applicationStorageMainService, + this.telemetryService); } return this.marketplaceHeadersPromise; From 500cd432b614e94f987e8487303c5ebb180d751f Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 27 Jun 2022 18:10:07 -0700 Subject: [PATCH 003/150] Ensure folding hint is hidden for cells that don't fold Fix #151150 --- .../contrib/notebook/browser/view/cellParts/foldedCellHint.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/foldedCellHint.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/foldedCellHint.ts index e8b61a303d0..44d8f209cbe 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/foldedCellHint.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/foldedCellHint.ts @@ -39,7 +39,7 @@ export class FoldedCellHint extends CellPart { const foldHintTop = element.layoutInfo.previewHeight; this._container.style.top = `${foldHintTop}px`; - } else if (element.foldingState === CellFoldingState.Expanded) { + } else { DOM.hide(this._container); } } From 2055001e6e05b83656882c461ba723d1c21c31e1 Mon Sep 17 00:00:00 2001 From: V360 Date: Mon, 27 Jun 2022 23:46:39 -0400 Subject: [PATCH 004/150] fix inlay hints being offset by 1px in some cases VS Code previously applied the `vertical-align: middle` CSS rule to all hints, even those that have the same font style as the editor's. This results in inlay hints being 1px lower than they should be. This pull request adds the `isUniform` flag to `_getLayoutInfo()`'s output. It indicates if the inlay hint styles and editor styles match. If they do, the `vertical-align` rule is set to `baseline`, effectively disabling it. --- .../inlayHints/browser/inlayHintsController.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts b/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts index 30c8a909369..a1252cb4bdf 100644 --- a/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts +++ b/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts @@ -468,7 +468,7 @@ export class InlayHintsController implements IEditorContribution { // - const { fontSize, fontFamily, padding } = this._getLayoutInfo(); + const { fontSize, fontFamily, padding, isUniform } = this._getLayoutInfo(); const fontFamilyVar = '--code-editorInlayHintsFontFamily'; this._editor.getContainerDomNode().style.setProperty(fontFamilyVar, fontFamily); @@ -493,7 +493,7 @@ export class InlayHintsController implements IEditorContribution { const cssProperties: CssProperties = { fontSize: `${fontSize}px`, fontFamily: `var(${fontFamilyVar}), ${EDITOR_FONT_DEFAULTS.fontFamily}`, - verticalAlign: 'middle', + verticalAlign: isUniform ? 'baseline' : 'middle', }; if (isNonEmptyArray(item.hint.textEdits)) { @@ -587,13 +587,23 @@ export class InlayHintsController implements IEditorContribution { private _getLayoutInfo() { const options = this._editor.getOption(EditorOption.inlayHints); + const padding = options.padding; + const editorFontSize = this._editor.getOption(EditorOption.fontSize); + const editorFontFamily = this._editor.getOption(EditorOption.fontFamily); + let fontSize = options.fontSize; if (!fontSize || fontSize < 5 || fontSize > editorFontSize) { fontSize = editorFontSize; } - const fontFamily = options.fontFamily || this._editor.getOption(EditorOption.fontFamily); - return { fontSize, fontFamily, padding: options.padding }; + + const fontFamily = options.fontFamily || editorFontFamily; + + const isUniform = !padding + && fontFamily === editorFontFamily + && fontSize === editorFontSize; + + return { fontSize, fontFamily, padding, isUniform }; } private _removeAllDecorations(): void { From 3aa87adc4365c37d46d03b3f3d6ce166c7c6356b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 28 Jun 2022 08:26:00 +0200 Subject: [PATCH 005/150] merge editor - fix URI check for view state (#153416) --- .../workbench/contrib/mergeEditor/browser/view/mergeEditor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts index 1b31a6532f5..001032afb25 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts @@ -459,7 +459,7 @@ export class MergeEditor extends AbstractTextEditor { } protected computeEditorViewState(resource: URI): IMergeEditorViewState | undefined { - if (isEqual(this.model?.result.uri, resource)) { + if (!isEqual(this.model?.result.uri, resource)) { // TODO@bpasero Why not check `input#resource` and don't ask me for "forgein" resources? return undefined; } From 7233eff3df3c6fae07588c00d4f08d41870578e3 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 28 Jun 2022 09:13:01 +0200 Subject: [PATCH 006/150] merge editor - caller reverts (#153415) --- .../workbench/contrib/mergeEditor/browser/mergeEditorInput.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts index 660eca9666b..b9447df7499 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts @@ -224,9 +224,7 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements return ConfirmResult.SAVE; } - // don't save: revert model and no save - console.assert(!isAnyAutoSave); - this._outTextModel?.revert({ force: true }); + // don't save return ConfirmResult.DONT_SAVE; } From ae617b7c7b67738516e3a94656825409e7f10bc2 Mon Sep 17 00:00:00 2001 From: Prashant Cholachagudda Date: Tue, 28 Jun 2022 13:27:31 +0530 Subject: [PATCH 007/150] Using NullTelemetryService instead of mock --- .../common/extensionGalleryService.test.ts | 56 +------------------ 1 file changed, 3 insertions(+), 53 deletions(-) diff --git a/src/vs/platform/extensionManagement/test/common/extensionGalleryService.test.ts b/src/vs/platform/extensionManagement/test/common/extensionGalleryService.test.ts index 4db659c6fc8..803066d7790 100644 --- a/src/vs/platform/extensionManagement/test/common/extensionGalleryService.test.ts +++ b/src/vs/platform/extensionManagement/test/common/extensionGalleryService.test.ts @@ -21,11 +21,9 @@ import product from 'vs/platform/product/common/product'; import { IProductService } from 'vs/platform/product/common/productService'; import { resolveMarketplaceHeaders } from 'vs/platform/externalServices/common/marketplace'; import { InMemoryStorageService, IStorageService } from 'vs/platform/storage/common/storage'; -import { ITelemetryInfo, ITelemetryService, TelemetryConfiguration, TelemetryLevel, TELEMETRY_SETTING_ID } from 'vs/platform/telemetry/common/telemetry'; +import { ITelemetryService, TelemetryConfiguration, TELEMETRY_SETTING_ID } from 'vs/platform/telemetry/common/telemetry'; import { TargetPlatform } from 'vs/platform/extensions/common/extensions'; -import { ClassifiedEvent, GDPRClassification, StrictPropertyCheck } from 'vs/platform/telemetry/common/gdprTypings'; -import { staticObservableValue } from 'vs/base/common/observableValue'; -import { Emitter, Event } from 'vs/base/common/event'; +import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; class EnvironmentServiceMock extends mock() { override readonly serviceMachineIdResource: URI; @@ -51,7 +49,7 @@ suite('Extension Gallery Service', () => { configurationService = new TestConfigurationService({ [TELEMETRY_SETTING_ID]: TelemetryConfiguration.ON }); configurationService.updateValue(TELEMETRY_SETTING_ID, TelemetryConfiguration.ON); productService = { _serviceBrand: undefined, ...product, enableTelemetry: true }; - telemetryService = new MockTelemetryService(); + telemetryService = NullTelemetryService; }); teardown(() => disposables.clear()); @@ -158,51 +156,3 @@ suite('Extension Gallery Service', () => { return { version, targetPlatform } as IRawGalleryExtensionVersion; } }); - -class MockTelemetryService implements ITelemetryService { - public _serviceBrand: undefined; - public telemetryLevel = staticObservableValue(TelemetryLevel.USAGE); - public sendErrorTelemetry = true; - - public events: any[] = []; - - private readonly emitter = new Emitter(); - - public get eventLogged(): Event { - return this.emitter.event; - } - - public setEnabled(value: boolean): void { - } - - public setExperimentProperty(name: string, value: string): void { - } - - public publicLog(eventName: string, data?: any): Promise { - const event = { name: eventName, data: data }; - this.events.push(event); - this.emitter.fire(event); - return Promise.resolve(); - } - - public publicLog2 = never, T extends GDPRClassification = never>(eventName: string, data?: StrictPropertyCheck) { - return this.publicLog(eventName, data as any); - } - - public publicLogError(eventName: string, data?: any): Promise { - return this.publicLog(eventName, data); - } - - public publicLogError2 = never, T extends GDPRClassification = never>(eventName: string, data?: StrictPropertyCheck) { - return this.publicLogError(eventName, data as any); - } - - public getTelemetryInfo(): Promise { - return Promise.resolve({ - instanceId: 'someValue.instanceId', - sessionId: 'someValue.sessionId', - machineId: 'someValue.machineId', - firstSessionDate: 'someValue.firstSessionDate' - }); - } -} From 44a89e5e8ee701e3275aebe8edc2c61451be826a Mon Sep 17 00:00:00 2001 From: Prashant Cholachagudda Date: Tue, 28 Jun 2022 13:32:51 +0530 Subject: [PATCH 008/150] remove redundant telemetryService variable --- .../test/common/extensionGalleryService.test.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/vs/platform/extensionManagement/test/common/extensionGalleryService.test.ts b/src/vs/platform/extensionManagement/test/common/extensionGalleryService.test.ts index 803066d7790..7dcb41d56bf 100644 --- a/src/vs/platform/extensionManagement/test/common/extensionGalleryService.test.ts +++ b/src/vs/platform/extensionManagement/test/common/extensionGalleryService.test.ts @@ -21,7 +21,7 @@ import product from 'vs/platform/product/common/product'; import { IProductService } from 'vs/platform/product/common/productService'; import { resolveMarketplaceHeaders } from 'vs/platform/externalServices/common/marketplace'; import { InMemoryStorageService, IStorageService } from 'vs/platform/storage/common/storage'; -import { ITelemetryService, TelemetryConfiguration, TELEMETRY_SETTING_ID } from 'vs/platform/telemetry/common/telemetry'; +import { TelemetryConfiguration, TELEMETRY_SETTING_ID } from 'vs/platform/telemetry/common/telemetry'; import { TargetPlatform } from 'vs/platform/extensions/common/extensions'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; @@ -37,7 +37,6 @@ class EnvironmentServiceMock extends mock() { suite('Extension Gallery Service', () => { const disposables: DisposableStore = new DisposableStore(); let fileService: IFileService, environmentService: IEnvironmentService, storageService: IStorageService, productService: IProductService, configurationService: IConfigurationService; - let telemetryService: ITelemetryService; setup(() => { const serviceMachineIdResource = joinPath(URI.file('tests').with({ scheme: 'vscode-tests' }), 'machineid'); @@ -49,15 +48,14 @@ suite('Extension Gallery Service', () => { configurationService = new TestConfigurationService({ [TELEMETRY_SETTING_ID]: TelemetryConfiguration.ON }); configurationService.updateValue(TELEMETRY_SETTING_ID, TelemetryConfiguration.ON); productService = { _serviceBrand: undefined, ...product, enableTelemetry: true }; - telemetryService = NullTelemetryService; }); teardown(() => disposables.clear()); test('marketplace machine id', async () => { - const headers = await resolveMarketplaceHeaders(product.version, productService, environmentService, configurationService, fileService, storageService, telemetryService); + const headers = await resolveMarketplaceHeaders(product.version, productService, environmentService, configurationService, fileService, storageService, NullTelemetryService); assert.ok(isUUID(headers['X-Market-User-Id'])); - const headers2 = await resolveMarketplaceHeaders(product.version, productService, environmentService, configurationService, fileService, storageService, telemetryService); + const headers2 = await resolveMarketplaceHeaders(product.version, productService, environmentService, configurationService, fileService, storageService, NullTelemetryService); assert.strictEqual(headers['X-Market-User-Id'], headers2['X-Market-User-Id']); }); From 5feeb83b0c0d57403f02a740b53bd02759561745 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 28 Jun 2022 11:21:45 +0200 Subject: [PATCH 009/150] Drag on + to leave multiline comment (#153310) Fixes #148214 --- .../browser/commentThreadZoneWidget.ts | 16 +++++++ .../browser/commentsEditorContribution.ts | 42 ++++++++++++++----- 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts index 32b36c7f42f..99c4aae1ca9 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts @@ -56,6 +56,22 @@ export function parseMouseDownInfoFromEvent(e: IEditorMouseEvent) { return { lineNumber: range.startLineNumber }; } +export function isMouseUpEventDragFromMouseDown(mouseDownInfo: { lineNumber: number } | null, e: IEditorMouseEvent) { + if (!mouseDownInfo) { + return null; + } + + const { lineNumber } = mouseDownInfo; + + const range = e.target.range; + + if (!range) { + return null; + } + + return lineNumber; +} + export function isMouseUpEventMatchMouseDown(mouseDownInfo: { lineNumber: number } | null, e: IEditorMouseEvent) { if (!mouseDownInfo) { return null; diff --git a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts index 04c4381ce3f..a0e155d0bdd 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts @@ -31,7 +31,7 @@ import { registerThemingParticipant } from 'vs/platform/theme/common/themeServic import { STATUS_BAR_ITEM_ACTIVE_BACKGROUND, STATUS_BAR_ITEM_HOVER_BACKGROUND } from 'vs/workbench/common/theme'; import { CommentGlyphWidget, overviewRulerCommentingRangeForeground } from 'vs/workbench/contrib/comments/browser/commentGlyphWidget'; import { ICommentInfo, ICommentService } from 'vs/workbench/contrib/comments/browser/commentService'; -import { isMouseUpEventMatchMouseDown, parseMouseDownInfoFromEvent, ReviewZoneWidget } from 'vs/workbench/contrib/comments/browser/commentThreadZoneWidget'; +import { isMouseUpEventDragFromMouseDown, parseMouseDownInfoFromEvent, ReviewZoneWidget } from 'vs/workbench/contrib/comments/browser/commentThreadZoneWidget'; import { ctxCommentEditorFocused, SimpleCommentEditor } from 'vs/workbench/contrib/comments/browser/simpleCommentEditor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; @@ -405,7 +405,12 @@ export class CommentController implements IEditorContribution { } private onEditorMouseMove(e: IEditorMouseEvent): void { - this._commentingRangeDecorator.updateHover(e.target.position?.lineNumber); + const position = e.target.position?.lineNumber; + if (e.event.leftButton.valueOf() && position && this.mouseDownInfo) { + this._commentingRangeDecorator.updateSelection(position, new Range(this.mouseDownInfo.lineNumber, 1, position, 1)); + } else { + this._commentingRangeDecorator.updateHover(position); + } } private onEditorChangeCursorSelection(e?: ICursorSelectionChangedEvent): void { @@ -691,22 +696,37 @@ export class CommentController implements IEditorContribution { } private onEditorMouseUp(e: IEditorMouseEvent): void { - const matchedLineNumber = isMouseUpEventMatchMouseDown(this.mouseDownInfo, e); + const matchedLineNumber = isMouseUpEventDragFromMouseDown(this.mouseDownInfo, e); this.mouseDownInfo = null; if (matchedLineNumber === null || !e.target.element) { return; } + const mouseUpIsOnDecorator = (e.target.element.className.indexOf('comment-diff-added') >= 0); - if (e.target.element.className.indexOf('comment-diff-added') >= 0) { - const lineNumber = e.target.position!.lineNumber; - // Check for selection at line number. - let range: Range = new Range(lineNumber, 1, lineNumber, 1); - const selection = this.editor.getSelection(); - if (selection && (selection.startLineNumber <= lineNumber) && (lineNumber <= selection.endLineNumber)) { - range = selection; - this.editor.setSelection(new Range(selection.endLineNumber, 1, selection.endLineNumber, 1)); + const lineNumber = e.target.position!.lineNumber; + let range: Range | undefined; + let selection: Range | null | undefined; + // Check for drag along gutter decoration + if ((matchedLineNumber !== lineNumber)) { + if (matchedLineNumber > lineNumber) { + selection = new Range(matchedLineNumber, this.editor.getModel()!.getLineLength(matchedLineNumber) + 1, lineNumber, 1); + } else { + selection = new Range(matchedLineNumber, 1, lineNumber, this.editor.getModel()!.getLineLength(lineNumber) + 1); } + } else if (mouseUpIsOnDecorator) { + selection = this.editor.getSelection(); + } + + // Check for selection at line number. + if (selection && (selection.startLineNumber <= lineNumber) && (lineNumber <= selection.endLineNumber)) { + range = selection; + this.editor.setSelection(new Range(selection.endLineNumber, 1, selection.endLineNumber, 1)); + } else if (mouseUpIsOnDecorator) { + range = new Range(lineNumber, 1, lineNumber, 1); + } + + if (range) { this.addOrToggleCommentAtLine(range, e); } } From 75b3fd5253ebe767f630e7644e7c1a23483e828e Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 28 Jun 2022 14:12:08 +0200 Subject: [PATCH 010/150] Git - Remove icons from branch picker to address regression (#153433) * Remove icons from branch picker to address regression * Fix create branch from issue * Pull request feedback --- extensions/git/src/commands.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 53d97a60e35..e519e1de1c9 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -28,6 +28,7 @@ class CheckoutItem implements QuickPickItem { protected get shortCommit(): string { return (this.ref.commit || '').substr(0, 8); } get label(): string { return `${this.repository.isBranchProtected(this.ref.name ?? '') ? '$(lock)' : '$(git-branch)'} ${this.ref.name || this.shortCommit}`; } get description(): string { return this.shortCommit; } + get refName(): string | undefined { return this.ref.name; } constructor(protected repository: Repository, protected ref: Ref) { } @@ -140,6 +141,7 @@ class HEADItem implements QuickPickItem { get label(): string { return 'HEAD'; } get description(): string { return (this.repository.HEAD && this.repository.HEAD.commit || '').substr(0, 8); } get alwaysShow(): boolean { return true; } + get refName(): string { return 'HEAD'; } } class AddRemoteItem implements QuickPickItem { @@ -1998,7 +2000,9 @@ export class CommandCenter { return; } - target = choice.label; + if (choice.refName) { + target = choice.refName; + } } await repository.branch(branchName, true, target); From cf0cb66d0e7ebc6e18ad53d6242e4f8cba7a076d Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 28 Jun 2022 14:42:35 +0200 Subject: [PATCH 011/150] remove old way of handling conflicts (#153456) --- .../userDataSync/browser/userDataSync.ts | 175 ++---------------- 1 file changed, 12 insertions(+), 163 deletions(-) diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 33629a1b8f6..a406183a623 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -6,10 +6,10 @@ import { Action } from 'vs/base/common/actions'; import { getErrorMessage, isCancellationError } from 'vs/base/common/errors'; import { Event } from 'vs/base/common/event'; -import { Disposable, DisposableStore, MutableDisposable, toDisposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, MutableDisposable, toDisposable, IDisposable } from 'vs/base/common/lifecycle'; import { isEqual, basename } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; -import { registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import type { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; import { ILanguageService } from 'vs/editor/common/languages/language'; @@ -59,10 +59,6 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { FloatingClickWidget } from 'vs/workbench/browser/codeeditor'; -import { IEditorContribution } from 'vs/editor/common/editorCommon'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EditorResolution } from 'vs/platform/editor/common/editor'; const CONTEXT_CONFLICTS_SOURCES = new RawContextKey('conflictsSources', ''); @@ -169,7 +165,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo this.registerViews(); textModelResolverService.registerTextModelContentProvider(USER_DATA_SYNC_SCHEME, instantiationService.createInstance(UserDataRemoteContentProvider)); - registerEditorContribution(AcceptChangesContribution.ID, AcceptChangesContribution); this._register(Event.any(userDataSyncService.onDidChangeStatus, userDataSyncEnablementService.onDidChangeEnablement) (() => this.turningOnSync = !userDataSyncEnablementService.isEnabled() && userDataSyncService.status !== SyncStatus.Idle)); @@ -730,35 +725,17 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } private async handleConflicts([syncResource, conflicts]: [SyncResource, IResourcePreview[]]): Promise { - const useMergeEditor = this.configurationService.getValue('settingsSync.mergeEditor') ?? true; for (const conflict of conflicts) { - if (useMergeEditor) { - const remoteResourceName = localize({ key: 'remoteResourceName', comment: ['remote as in file in cloud'] }, "{0} (Remote)", basename(conflict.remoteResource)); - const localResourceName = localize('localResourceName', "{0} (Local)", basename(conflict.remoteResource)); - const input = this.instantiationService.createInstance( - MergeEditorInput, - conflict.baseResource, - { title: localize('Yours', 'Yours'), description: localResourceName, detail: undefined, uri: conflict.localResource }, - { title: localize('Theirs', 'Theirs'), description: remoteResourceName, detail: undefined, uri: conflict.remoteResource }, - conflict.previewResource, - ); - await this.editorService.openEditor(input); - } else { - const leftResourceName = localize({ key: 'leftResourceName', comment: ['remote as in file in cloud'] }, "{0} (Remote)", basename(conflict.remoteResource)); - const rightResourceName = localize('merges', "{0} (Merges)", basename(conflict.previewResource)); - await this.editorService.openEditor({ - original: { resource: conflict.remoteResource }, - modified: { resource: conflict.previewResource }, - label: localize('sideBySideLabels', "{0} ↔ {1}", leftResourceName, rightResourceName), - description: localize('sideBySideDescription', "Settings Sync"), - options: { - preserveFocus: false, - pinned: true, - revealIfVisible: true, - override: EditorResolution.DISABLED - }, - }); - } + const remoteResourceName = localize({ key: 'remoteResourceName', comment: ['remote as in file in cloud'] }, "{0} (Remote)", basename(conflict.remoteResource)); + const localResourceName = localize('localResourceName', "{0} (Local)", basename(conflict.remoteResource)); + const input = this.instantiationService.createInstance( + MergeEditorInput, + conflict.baseResource, + { title: localize('Yours', 'Yours'), description: localResourceName, detail: undefined, uri: conflict.localResource }, + { title: localize('Theirs', 'Theirs'), description: remoteResourceName, detail: undefined, uri: conflict.remoteResource }, + conflict.previewResource, + ); + await this.editorService.openEditor(input); } } @@ -1394,131 +1371,3 @@ class UserDataRemoteContentProvider implements ITextModelContentProvider { return null; } } - -class AcceptChangesContribution extends Disposable implements IEditorContribution { - - static get(editor: ICodeEditor): AcceptChangesContribution | null { - return editor.getContribution(AcceptChangesContribution.ID); - } - - public static readonly ID = 'editor.contrib.acceptChangesButton'; - - private acceptChangesButton: FloatingClickWidget | undefined; - - constructor( - private editor: ICodeEditor, - @IInstantiationService private readonly instantiationService: IInstantiationService, - @IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService, - @INotificationService private readonly notificationService: INotificationService, - @IDialogService private readonly dialogService: IDialogService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @ITelemetryService private readonly telemetryService: ITelemetryService, - @IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService, - ) { - super(); - - this.update(); - this.registerListeners(); - } - - private registerListeners(): void { - this._register(this.editor.onDidChangeModel(() => this.update())); - this._register(this.userDataSyncService.onDidChangeConflicts(() => this.update())); - this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('diffEditor.renderSideBySide'))(() => this.update())); - } - - private update(): void { - if (!this.shouldShowButton(this.editor)) { - this.disposeAcceptChangesWidgetRenderer(); - return; - } - - this.createAcceptChangesWidgetRenderer(); - } - - private shouldShowButton(editor: ICodeEditor): boolean { - const model = editor.getModel(); - if (!model) { - return false; // we need a model - } - - if (!this.userDataSyncEnablementService.isEnabled()) { - return false; - } - - const syncResourceConflicts = this.getSyncResourceConflicts(model.uri); - if (!syncResourceConflicts) { - return false; - } - - if (syncResourceConflicts[1].some(({ previewResource }) => isEqual(previewResource, model.uri))) { - return true; - } - - if (syncResourceConflicts[1].some(({ remoteResource }) => isEqual(remoteResource, model.uri))) { - return this.configurationService.getValue('diffEditor.renderSideBySide'); - } - - return false; - } - - private createAcceptChangesWidgetRenderer(): void { - if (!this.acceptChangesButton) { - const resource = this.editor.getModel()!.uri; - const [syncResource, conflicts] = this.getSyncResourceConflicts(resource)!; - const isRemote = conflicts.some(({ remoteResource }) => isEqual(remoteResource, resource)); - const acceptRemoteLabel = localize('accept remote', "Accept Remote"); - const acceptMergesLabel = localize('accept merges', "Accept Merges"); - const acceptRemoteButtonLabel = localize('accept remote button', "Accept &&Remote"); - const acceptMergesButtonLabel = localize('accept merges button', "Accept &&Merges"); - this.acceptChangesButton = this.instantiationService.createInstance(FloatingClickWidget, this.editor, isRemote ? acceptRemoteLabel : acceptMergesLabel, null); - this._register(this.acceptChangesButton.onClick(async () => { - const model = this.editor.getModel(); - if (model) { - this.telemetryService.publicLog2<{ source: string; action: string }, SyncConflictsClassification>('sync/handleConflicts', { source: syncResource, action: isRemote ? 'acceptRemote' : 'acceptLocal' }); - const syncAreaLabel = getSyncAreaLabel(syncResource); - const result = await this.dialogService.confirm({ - type: 'info', - title: isRemote - ? localize('Sync accept remote', "{0}: {1}", SYNC_TITLE, acceptRemoteLabel) - : localize('Sync accept merges', "{0}: {1}", SYNC_TITLE, acceptMergesLabel), - message: isRemote - ? localize('confirm replace and overwrite local', "Would you like to accept remote {0} and replace local {1}?", syncAreaLabel.toLowerCase(), syncAreaLabel.toLowerCase()) - : localize('confirm replace and overwrite remote', "Would you like to accept merges and replace remote {0}?", syncAreaLabel.toLowerCase()), - primaryButton: isRemote ? acceptRemoteButtonLabel : acceptMergesButtonLabel - }); - if (result.confirmed) { - try { - await this.userDataSyncService.accept(syncResource, model.uri, model.getValue(), true); - } catch (e) { - if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.LocalPreconditionFailed) { - const syncResourceCoflicts = this.userDataSyncService.conflicts.filter(syncResourceCoflicts => syncResourceCoflicts[0] === syncResource)[0]; - if (syncResourceCoflicts && conflicts.some(conflict => isEqual(conflict.previewResource, model.uri) || isEqual(conflict.remoteResource, model.uri))) { - this.notificationService.warn(localize('update conflicts', "Could not resolve conflicts as there is new local version available. Please try again.")); - } - } else { - this.notificationService.error(localize('accept failed', "Error while accepting changes. Please check [logs]({0}) for more details.", `command:${SHOW_SYNC_LOG_COMMAND_ID}`)); - } - } - } - } - })); - - this.acceptChangesButton.render(); - } - } - - private getSyncResourceConflicts(resource: URI): [SyncResource, IResourcePreview[]] | undefined { - return this.userDataSyncService.conflicts.filter(([, conflicts]) => conflicts.some(({ previewResource, remoteResource }) => isEqual(previewResource, resource) || isEqual(remoteResource, resource)))[0]; - } - - private disposeAcceptChangesWidgetRenderer(): void { - dispose(this.acceptChangesButton); - this.acceptChangesButton = undefined; - } - - override dispose(): void { - this.disposeAcceptChangesWidgetRenderer(); - super.dispose(); - } -} From c7bb86414ed47371f37eee3a33e2bbf035cd4a3f Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Tue, 28 Jun 2022 14:53:19 +0200 Subject: [PATCH 012/150] Avoid `editor.deltaDecorations` (#153463) Fixes #153215: Avoid `editor.deltaDecorations` --- src/vs/editor/browser/editorBrowser.ts | 13 +++++++++---- .../services/abstractCodeEditorService.ts | 2 +- src/vs/editor/browser/widget/codeEditorWidget.ts | 16 +++++++++++++--- .../gotoSymbol/browser/peek/referencesWidget.ts | 14 ++++++++------ src/vs/monaco.d.ts | 4 ++++ src/vs/workbench/api/browser/mainThreadEditor.ts | 4 ++-- .../contrib/comments/browser/commentReply.ts | 2 +- .../contrib/debug/browser/breakpointWidget.ts | 2 +- src/vs/workbench/contrib/debug/browser/repl.ts | 2 +- .../interactive/browser/interactiveEditor.ts | 2 +- 10 files changed, 41 insertions(+), 20 deletions(-) diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index e620b237b81..0fc6f298ccc 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -857,19 +857,24 @@ export interface ICodeEditor extends editorCommon.IEditor { deltaDecorations(oldDecorations: string[], newDecorations: IModelDeltaDecoration[]): string[]; /** - * @internal + * Remove previously added decorations. */ - setDecorations(description: string, decorationTypeKey: string, ranges: editorCommon.IDecorationOptions[]): void; + removeDecorations(decorationIds: string[]): void; /** * @internal */ - setDecorationsFast(decorationTypeKey: string, ranges: IRange[]): void; + setDecorationsByType(description: string, decorationTypeKey: string, ranges: editorCommon.IDecorationOptions[]): void; /** * @internal */ - removeDecorations(decorationTypeKey: string): void; + setDecorationsByTypeFast(decorationTypeKey: string, ranges: IRange[]): void; + + /** + * @internal + */ + removeDecorationsByType(decorationTypeKey: string): void; /** * Get the layout info for the editor. diff --git a/src/vs/editor/browser/services/abstractCodeEditorService.ts b/src/vs/editor/browser/services/abstractCodeEditorService.ts index da58f07c2e7..761c4eee60f 100644 --- a/src/vs/editor/browser/services/abstractCodeEditorService.ts +++ b/src/vs/editor/browser/services/abstractCodeEditorService.ts @@ -161,7 +161,7 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC if (provider.refCount <= 0) { this._decorationOptionProviders.delete(key); provider.dispose(); - this.listCodeEditors().forEach((ed) => ed.removeDecorations(key)); + this.listCodeEditors().forEach((ed) => ed.removeDecorationsByType(key)); } } } diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 75810f5c95e..25e647366c8 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -1298,7 +1298,17 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE return this._modelData.model.deltaDecorations(oldDecorations, newDecorations, this._id); } - public setDecorations(description: string, decorationTypeKey: string, decorationOptions: editorCommon.IDecorationOptions[]): void { + public removeDecorations(decorationIds: string[]): void { + if (!this._modelData || decorationIds.length === 0) { + return; + } + + this._modelData.model.changeDecorations((changeAccessor) => { + changeAccessor.deltaDecorations(decorationIds, []); + }); + } + + public setDecorationsByType(description: string, decorationTypeKey: string, decorationOptions: editorCommon.IDecorationOptions[]): void { const newDecorationsSubTypes: { [key: string]: boolean } = {}; const oldDecorationsSubTypes = this._decorationTypeSubtypes[decorationTypeKey] || {}; @@ -1340,7 +1350,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE this._decorationTypeKeysToIds[decorationTypeKey] = this.deltaDecorations(oldDecorationsIds, newModelDecorations); } - public setDecorationsFast(decorationTypeKey: string, ranges: IRange[]): void { + public setDecorationsByTypeFast(decorationTypeKey: string, ranges: IRange[]): void { // remove decoration sub types that are no longer used, deregister decoration type if necessary const oldDecorationsSubTypes = this._decorationTypeSubtypes[decorationTypeKey] || {}; @@ -1360,7 +1370,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE this._decorationTypeKeysToIds[decorationTypeKey] = this.deltaDecorations(oldDecorationsIds, newModelDecorations); } - public removeDecorations(decorationTypeKey: string): void { + public removeDecorationsByType(decorationTypeKey: string): void { // remove decorations for type and sub type const oldDecorationsIds = this._decorationTypeKeysToIds[decorationTypeKey]; if (oldDecorationsIds) { diff --git a/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesWidget.ts b/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesWidget.ts index fd70d871ad7..ee7ef53aa64 100644 --- a/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesWidget.ts +++ b/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesWidget.ts @@ -100,10 +100,12 @@ class DecorationsManager implements IDisposable { newDecorationsActualIndex.push(i); } - const decorations = this._editor.deltaDecorations([], newDecorations); - for (let i = 0; i < decorations.length; i++) { - this._decorations.set(decorations[i], reference.children[newDecorationsActualIndex[i]]); - } + this._editor.changeDecorations((changeAccessor) => { + const decorations = changeAccessor.deltaDecorations([], newDecorations); + for (let i = 0; i < decorations.length; i++) { + this._decorations.set(decorations[i], reference.children[newDecorationsActualIndex[i]]); + } + }); } private _onDecorationChanged(): void { @@ -151,11 +153,11 @@ class DecorationsManager implements IDisposable { for (let i = 0, len = toRemove.length; i < len; i++) { this._decorations.delete(toRemove[i]); } - this._editor.deltaDecorations(toRemove, []); + this._editor.removeDecorations(toRemove); } removeDecorations(): void { - this._editor.deltaDecorations([...this._decorations.keys()], []); + this._editor.removeDecorations([...this._decorations.keys()]); this._decorations.clear(); } } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 20ba9975b73..03400969c32 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -5316,6 +5316,10 @@ declare namespace monaco.editor { * @see {@link ITextModel.deltaDecorations} */ deltaDecorations(oldDecorations: string[], newDecorations: IModelDeltaDecoration[]): string[]; + /** + * Remove previously added decorations. + */ + removeDecorations(decorationIds: string[]): void; /** * Get the layout info for the editor. */ diff --git a/src/vs/workbench/api/browser/mainThreadEditor.ts b/src/vs/workbench/api/browser/mainThreadEditor.ts index edc0ede1509..638fe8aa474 100644 --- a/src/vs/workbench/api/browser/mainThreadEditor.ts +++ b/src/vs/workbench/api/browser/mainThreadEditor.ts @@ -415,7 +415,7 @@ export class MainThreadTextEditor { if (!this._codeEditor) { return; } - this._codeEditor.setDecorations('exthost-api', key, ranges); + this._codeEditor.setDecorationsByType('exthost-api', key, ranges); } public setDecorationsFast(key: string, _ranges: number[]): void { @@ -426,7 +426,7 @@ export class MainThreadTextEditor { for (let i = 0, len = Math.floor(_ranges.length / 4); i < len; i++) { ranges[i] = new Range(_ranges[4 * i], _ranges[4 * i + 1], _ranges[4 * i + 2], _ranges[4 * i + 3]); } - this._codeEditor.setDecorationsFast(key, ranges); + this._codeEditor.setDecorationsByTypeFast(key, ranges); } public revealRange(range: IRange, revealType: TextEditorRevealType): void { diff --git a/src/vs/workbench/contrib/comments/browser/commentReply.ts b/src/vs/workbench/contrib/comments/browser/commentReply.ts index 87b195c52f8..0905db307c3 100644 --- a/src/vs/workbench/contrib/comments/browser/commentReply.ts +++ b/src/vs/workbench/contrib/comments/browser/commentReply.ts @@ -191,7 +191,7 @@ export class CommentReply extends Disposable { } }]; - this.commentEditor.setDecorations('review-zone-widget', COMMENTEDITOR_DECORATION_KEY, decorations); + this.commentEditor.setDecorationsByType('review-zone-widget', COMMENTEDITOR_DECORATION_KEY, decorations); } } diff --git a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts index 3df0cf71a74..fb873a5fc69 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts @@ -238,7 +238,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi const setDecorations = () => { const value = this.input.getModel().getValue(); const decorations = !!value ? [] : createDecorations(this.themeService.getColorTheme(), this.placeholder); - this.input.setDecorations('breakpoint-widget', DECORATION_KEY, decorations); + this.input.setDecorationsByType('breakpoint-widget', DECORATION_KEY, decorations); }; this.input.getModel().onDidChangeContent(() => setDecorations()); this.themeService.onDidColorThemeChange(() => setDecorations()); diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index a38c878e3bc..7fcf2fa1c08 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -718,7 +718,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { }); } - this.replInput.setDecorations('repl-decoration', DECORATION_KEY, decorations); + this.replInput.setDecorationsByType('repl-decoration', DECORATION_KEY, decorations); } override saveState(): void { diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts index c6e561c2c4d..37d2b4fde50 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts @@ -620,7 +620,7 @@ export class InteractiveEditor extends EditorPane { }); } - this.#codeEditorWidget.setDecorations('interactive-decoration', DECORATION_KEY, decorations); + this.#codeEditorWidget.setDecorationsByType('interactive-decoration', DECORATION_KEY, decorations); } override focus() { From 258ae8206b36dcd08c1ec8194bc0cab1ad5c6dec Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 28 Jun 2022 14:40:26 +0200 Subject: [PATCH 013/150] Adds some merge editor tests & diffing bug fix. --- .../common/services/editorSimpleWorker.ts | 14 +- .../mergeEditor/browser/mergeEditorInput.ts | 4 +- .../mergeEditor/browser/model/diffComputer.ts | 4 +- .../browser/model/mergeEditorModel.ts | 16 +- .../mergeEditor/test/browser/model.test.ts | 269 ++++++++++++++++++ 5 files changed, 290 insertions(+), 17 deletions(-) create mode 100644 src/vs/workbench/contrib/mergeEditor/test/browser/model.test.ts diff --git a/src/vs/editor/common/services/editorSimpleWorker.ts b/src/vs/editor/common/services/editorSimpleWorker.ts index cc9b43f6d24..d68936dbda4 100644 --- a/src/vs/editor/common/services/editorSimpleWorker.ts +++ b/src/vs/editor/common/services/editorSimpleWorker.ts @@ -11,7 +11,7 @@ import { IRequestHandler } from 'vs/base/common/worker/simpleWorker'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { DiffComputer, IChange, IDiffComputationResult } from 'vs/editor/common/diff/diffComputer'; -import { EndOfLineSequence } from 'vs/editor/common/model'; +import { EndOfLineSequence, ITextModel } from 'vs/editor/common/model'; import { IMirrorTextModel, IModelChangedEvent, MirrorTextModel as BaseMirrorModel } from 'vs/editor/common/model/mirrorTextModel'; import { ensureValidWordDefinition, getWordAtText, IWordAtPosition } from 'vs/editor/common/core/wordHelper'; import { IInplaceReplaceSupportResult, ILink, TextEdit } from 'vs/editor/common/languages'; @@ -388,8 +388,12 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { return null; } - const originalLines = original.getLinesContent(); - const modifiedLines = modified.getLinesContent(); + return EditorSimpleWorker.computeDiff(original, modified, ignoreTrimWhitespace, maxComputationTime); + } + + public static computeDiff(originalTextModel: ICommonModel | ITextModel, modifiedTextModel: ICommonModel | ITextModel, ignoreTrimWhitespace: boolean, maxComputationTime: number): IDiffComputationResult | null { + const originalLines = originalTextModel.getLinesContent(); + const modifiedLines = modifiedTextModel.getLinesContent(); const diffComputer = new DiffComputer(originalLines, modifiedLines, { shouldComputeCharChanges: true, shouldPostProcessCharChanges: true, @@ -399,7 +403,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { }); const diffResult = diffComputer.computeDiff(); - const identical = (diffResult.changes.length > 0 ? false : this._modelsAreIdentical(original, modified)); + const identical = (diffResult.changes.length > 0 ? false : this._modelsAreIdentical(originalTextModel, modifiedTextModel)); return { quitEarly: diffResult.quitEarly, identical: identical, @@ -407,7 +411,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { }; } - private _modelsAreIdentical(original: ICommonModel, modified: ICommonModel): boolean { + private static _modelsAreIdentical(original: ICommonModel | ITextModel, modified: ICommonModel | ITextModel): boolean { const originalLineCount = original.getLineCount(); const modifiedLineCount = modified.getLineCount(); if (originalLineCount !== modifiedLineCount) { diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts index 6526f2eeee0..a3412f829c3 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts @@ -16,6 +16,7 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { IEditorIdentifier, IUntypedEditorInput } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { AbstractTextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput'; +import { EditorWorkerServiceDiffComputer } from 'vs/workbench/contrib/mergeEditor/browser/model/diffComputer'; import { autorun } from 'vs/workbench/contrib/audioCues/browser/observable'; import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -107,7 +108,8 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements this.input2.title, this.input2.detail, this.input2.description, - result.object.textEditorModel + result.object.textEditorModel, + this._instaService.createInstance(EditorWorkerServiceDiffComputer), ); await this._model.onInitialized; diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/diffComputer.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/diffComputer.ts index 66116c85bb8..06401136b7a 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/model/diffComputer.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/model/diffComputer.ts @@ -24,7 +24,7 @@ export class EditorWorkerServiceDiffComputer implements IDiffComputer { async computeDiff(textModel1: ITextModel, textModel2: ITextModel): Promise { const diffs = await this.editorWorkerService.computeDiff(textModel1.uri, textModel2.uri, false, 1000); - if (!diffs || diffs.quitEarly) { + if (!diffs) { return { diffs: null }; } return { diffs: EditorWorkerServiceDiffComputer.fromDiffComputationResult(diffs, textModel1, textModel2) }; @@ -53,7 +53,7 @@ function fromLineChange(lineChange: ILineChange, originalTextModel: ITextModel, } let innerDiffs = lineChange.charChanges?.map(c => rangeMappingFromCharChange(c, originalTextModel, modifiedTextModel)).filter(isDefined); - if (!innerDiffs) { + if (!innerDiffs || innerDiffs.length === 0) { innerDiffs = [rangeMappingFromLineRanges(originalRange, modifiedRange)]; } diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts index ad978496388..a03d95ea043 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts @@ -3,25 +3,24 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { compareBy, CompareResult, tieBreakComparators, equals, numberComparator } from 'vs/base/common/arrays'; +import { compareBy, CompareResult, equals, numberComparator, tieBreakComparators } from 'vs/base/common/arrays'; import { BugIndicatingError } from 'vs/base/common/errors'; import { splitLines } from 'vs/base/common/strings'; import { Constants } from 'vs/base/common/uint'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; +import { ILanguageService } from 'vs/editor/common/languages/language'; import { ITextModel } from 'vs/editor/common/model'; -import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; +import { IModelService } from 'vs/editor/common/services/model'; import { EditorModel } from 'vs/workbench/common/editor/editorModel'; import { autorunHandleChanges, derivedObservable, IObservable, IReader, ITransaction, keepAlive, ObservableValue, transaction, waitForState } from 'vs/workbench/contrib/audioCues/browser/observable'; -import { EditorWorkerServiceDiffComputer } from 'vs/workbench/contrib/mergeEditor/browser/model/diffComputer'; -import { DetailedLineRangeMapping, DocumentMapping, LineRangeMapping } from 'vs/workbench/contrib/mergeEditor/browser/model/mapping'; +import { IDiffComputer } from 'vs/workbench/contrib/mergeEditor/browser/model/diffComputer'; import { LineRangeEdit, RangeEdit } from 'vs/workbench/contrib/mergeEditor/browser/model/editing'; import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange'; +import { DetailedLineRangeMapping, DocumentMapping, LineRangeMapping } from 'vs/workbench/contrib/mergeEditor/browser/model/mapping'; import { TextModelDiffChangeReason, TextModelDiffs, TextModelDiffState } from 'vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs'; -import { concatArrays, leftJoin, elementAtOrUndefined } from 'vs/workbench/contrib/mergeEditor/browser/utils'; +import { concatArrays, elementAtOrUndefined, leftJoin } from 'vs/workbench/contrib/mergeEditor/browser/utils'; import { ModifiedBaseRange, ModifiedBaseRangeState } from './modifiedBaseRange'; -import { IModelService } from 'vs/editor/common/services/model'; -import { ILanguageService } from 'vs/editor/common/languages/language'; export const enum MergeEditorModelState { initializing = 1, @@ -30,7 +29,6 @@ export const enum MergeEditorModelState { } export class MergeEditorModel extends EditorModel { - private readonly diffComputer = new EditorWorkerServiceDiffComputer(this.editorWorkerService); private readonly input1TextModelDiffs = this._register(new TextModelDiffs(this.base, this.input1, this.diffComputer)); private readonly input2TextModelDiffs = this._register(new TextModelDiffs(this.base, this.input2, this.diffComputer)); private readonly resultTextModelDiffs = this._register(new TextModelDiffs(this.base, this.result, this.diffComputer)); @@ -138,7 +136,7 @@ export class MergeEditorModel extends EditorModel { readonly input2Detail: string | undefined, readonly input2Description: string | undefined, readonly result: ITextModel, - @IEditorWorkerService private readonly editorWorkerService: IEditorWorkerService, + private readonly diffComputer: IDiffComputer, @IModelService private readonly modelService: IModelService, @ILanguageService private readonly languageService: ILanguageService, ) { diff --git a/src/vs/workbench/contrib/mergeEditor/test/browser/model.test.ts b/src/vs/workbench/contrib/mergeEditor/test/browser/model.test.ts new file mode 100644 index 00000000000..9963829694f --- /dev/null +++ b/src/vs/workbench/contrib/mergeEditor/test/browser/model.test.ts @@ -0,0 +1,269 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert = require('assert'); +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { Range } from 'vs/editor/common/core/range'; +import { ITextModel } from 'vs/editor/common/model'; +import { EditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker'; +import { createModelServices, createTextModel } from 'vs/editor/test/common/testTextModel'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { transaction } from 'vs/workbench/contrib/audioCues/browser/observable'; +import { EditorWorkerServiceDiffComputer } from 'vs/workbench/contrib/mergeEditor/browser/model/diffComputer'; +import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel'; + +suite('merge editor model', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + + test('prepend line', async () => { + await testMergeModel( + { + "languageId": "plaintext", + "base": "line1\nline2", + "input1": "0\nline1\nline2", + "input2": "0\nline1\nline2", + "result": "" + }, + model => { + assert.deepStrictEqual(model.getProjections(), { + base: '⟦⟧₀line1\nline2', + input1: '⟦0\n⟧₀line1\nline2', + input2: '⟦0\n⟧₀line1\nline2', + }); + + model.toggleConflict(0, 1); + assert.deepStrictEqual( + { result: model.getResult() }, + { result: '0\nline1\nline2' } + ); + + model.toggleConflict(0, 2); + assert.deepStrictEqual( + { result: model.getResult() }, + ({ result: "0\n0\nline1\nline2" }) + ); + } + ); + }); + + test('empty base', async () => { + await testMergeModel( + { + "languageId": "plaintext", + "base": "", + "input1": "input1", + "input2": "input2", + "result": "" + }, + model => { + assert.deepStrictEqual(model.getProjections(), ({ base: "⟦⟧₀", input1: "⟦input1⟧₀", input2: "⟦input2⟧₀" })); + + model.toggleConflict(0, 1); + assert.deepStrictEqual( + { result: model.getResult() }, + ({ result: "input1" }) + ); + + model.toggleConflict(0, 2); + assert.deepStrictEqual( + { result: model.getResult() }, + ({ result: "input2" }) + ); + } + ); + }); + + test('can merge word changes', async () => { + await testMergeModel( + { + "languageId": "plaintext", + "base": "hello", + "input1": "hallo", + "input2": "helloworld", + "result": "" + }, + model => { + assert.deepStrictEqual(model.getProjections(), { + base: '⟦hello⟧₀', + input1: '⟦hallo⟧₀', + input2: '⟦helloworld⟧₀', + }); + + model.toggleConflict(0, 1); + model.toggleConflict(0, 2); + + assert.deepStrictEqual( + { result: model.getResult() }, + { result: 'halloworld' } + ); + } + ); + + }); + + test('can combine insertions at end of document', async () => { + await testMergeModel( + { + "languageId": "plaintext", + "base": "Zürich\nBern\nBasel\nChur\nGenf\nThun", + "input1": "Zürich\nBern\nChur\nDavos\nGenf\nThun\nfunction f(b:boolean) {}", + "input2": "Zürich\nBern\nBasel (FCB)\nChur\nGenf\nThun\nfunction f(a:number) {}", + "result": "Zürich\nBern\nBasel\nChur\nDavos\nGenf\nThun" + }, + model => { + assert.deepStrictEqual(model.getProjections(), { + base: 'Zürich\nBern\n⟦Basel\n⟧₀Chur\n⟦⟧₁Genf\nThun⟦⟧₂', + input1: + 'Zürich\nBern\n⟦⟧₀Chur\n⟦Davos\n⟧₁Genf\nThun\n⟦function f(b:boolean) {}⟧₂', + input2: + 'Zürich\nBern\n⟦Basel (FCB)\n⟧₀Chur\n⟦⟧₁Genf\nThun\n⟦function f(a:number) {}⟧₂', + }); + + model.toggleConflict(2, 1); + model.toggleConflict(2, 2); + + assert.deepStrictEqual( + { result: model.getResult() }, + { + result: + 'Zürich\nBern\nBasel\nChur\nDavos\nGenf\nThun\nfunction f(b:boolean) {}\nfunction f(a:number) {}', + } + ); + } + ); + }); +}); + +async function testMergeModel( + options: MergeModelOptions, + fn: (model: MergeModelInterface) => void +): Promise { + const disposables = new DisposableStore(); + const modelInterface = disposables.add( + new MergeModelInterface(options, createModelServices(disposables)) + ); + await modelInterface.mergeModel.onInitialized; + await fn(modelInterface); + disposables.dispose(); +} + +interface MergeModelOptions { + languageId: string; + input1: string; + input2: string; + base: string; + result: string; +} + +function toSmallNumbersDec(value: number): string { + const smallNumbers = ['₀', '₁', '₂', '₃', '₄', '₅', '₆', '₇', '₈', '₉']; + return value.toString().split('').map(c => smallNumbers[parseInt(c)]).join(''); +} + +class MergeModelInterface extends Disposable { + public readonly mergeModel: MergeEditorModel; + + constructor(options: MergeModelOptions, instantiationService: IInstantiationService) { + super(); + const input1TextModel = this._register(createTextModel(options.input1, options.languageId)); + const input2TextModel = this._register(createTextModel(options.input2, options.languageId)); + const baseTextModel = this._register(createTextModel(options.base, options.languageId)); + const resultTextModel = this._register(createTextModel(options.result, options.languageId)); + this.mergeModel = this._register(instantiationService.createInstance(MergeEditorModel, + baseTextModel, + input1TextModel, + '', + '', + '', + input2TextModel, + '', + '', + '', + resultTextModel, + { + async computeDiff(textModel1, textModel2) { + const result = EditorSimpleWorker.computeDiff(textModel1, textModel2, false, 10000); + if (!result) { + return { diffs: null }; + } + return { + diffs: EditorWorkerServiceDiffComputer.fromDiffComputationResult( + result, + textModel1, + textModel2 + ), + }; + }, + } + )); + } + + getProjections(): unknown { + interface LabeledRange { + range: Range; + label: string; + } + function applyRanges(textModel: ITextModel, ranges: LabeledRange[]): void { + textModel.applyEdits(ranges.map(({ range, label }) => ({ + range: range, + text: `⟦${textModel.getValueInRange(range)}⟧${label}`, + }))); + } + const baseRanges = this.mergeModel.modifiedBaseRanges.get(); + + const baseTextModel = createTextModel(this.mergeModel.base.getValue()); + applyRanges( + baseTextModel, + baseRanges.map((r, idx) => ({ + range: r.baseRange.toRange(), + label: toSmallNumbersDec(idx), + })) + ); + + const input1TextModel = createTextModel(this.mergeModel.input1.getValue()); + applyRanges( + input1TextModel, + baseRanges.map((r, idx) => ({ + range: r.input1Range.toRange(), + label: toSmallNumbersDec(idx), + })) + ); + + const input2TextModel = createTextModel(this.mergeModel.input2.getValue()); + applyRanges( + input2TextModel, + baseRanges.map((r, idx) => ({ + range: r.input2Range.toRange(), + label: toSmallNumbersDec(idx), + })) + ); + + const result = { + base: baseTextModel.getValue(), + input1: input1TextModel.getValue(), + input2: input2TextModel.getValue(), + }; + baseTextModel.dispose(); + input1TextModel.dispose(); + input2TextModel.dispose(); + return result; + } + + toggleConflict(conflictIdx: number, inputNumber: 1 | 2): void { + const baseRange = this.mergeModel.modifiedBaseRanges.get()[conflictIdx]; + if (!baseRange) { + throw new Error(); + } + const state = this.mergeModel.getState(baseRange).get(); + transaction(tx => { + this.mergeModel.setState(baseRange, state.toggle(inputNumber), true, tx); + }); + } + + getResult(): string { + return this.mergeModel.result.getValue(); + } +} From 7b753f406b79fd2e3e3a7dfa7839cbd8b341337f Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 28 Jun 2022 14:30:28 +0200 Subject: [PATCH 014/150] Updates 3wm color ids. --- .../mergeEditor/browser/view/colors.ts | 112 ++++-------------- .../browser/view/media/mergeEditor.css | 12 +- 2 files changed, 30 insertions(+), 94 deletions(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/colors.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/colors.ts index 99a4125d8e4..ac93d328631 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/colors.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/colors.ts @@ -7,114 +7,50 @@ import { localize } from 'vs/nls'; import { registerColor } from 'vs/platform/theme/common/colorRegistry'; export const diff = registerColor( - 'mergeEditor.diff', - { - dark: '#9bb95533', - light: '#9bb95533', - hcDark: '#9bb95533', - hcLight: '#9bb95533', - }, - localize( - 'mergeEditor.diff', - 'The foreground color for changes.' - ) + 'mergeEditor.change.background', + { dark: '#9bb95533', light: '#9bb95533', hcDark: '#9bb95533', hcLight: '#9bb95533', }, + localize('mergeEditor.change.background', 'The background color for changes.') ); export const diffWord = registerColor( - 'mergeEditor.diff.word', - { - dark: '#9bb9551e', - light: '#9bb9551e', - hcDark: '#9bb9551e', - hcLight: '#9bb9551e', - }, - localize( - 'mergeEditor.diff.word', - 'The foreground color for word changes.' - ) + 'mergeEditor.change.word.background', + { dark: '#9bb9551e', light: '#9bb9551e', hcDark: '#9bb9551e', hcLight: '#9bb9551e', }, + localize('mergeEditor.change.word.background', 'The background color for word changes.') ); export const conflictBorderUnhandledUnfocused = registerColor( - 'mergeEditor.conflictBorder.unhandledUnfocused', - { - dark: '#ffa6007a', - light: '#ffa6007a', - hcDark: '#ffa6007a', - hcLight: '#ffa6007a', - }, - localize( - 'mergeEditor.conflictBorder.unhandledUnfocused', - 'The border color of unhandled unfocused conflicts.' - ) + 'mergeEditor.conflict.unhandledUnfocused.border', + { dark: '#ffa6007a', light: '#ffa6007a', hcDark: '#ffa6007a', hcLight: '#ffa6007a', }, + localize('mergeEditor.conflict.unhandledUnfocused.border', 'The border color of unhandled unfocused conflicts.') ); export const conflictBorderUnhandledFocused = registerColor( - 'mergeEditor.conflictBorder.unhandledFocused', - { - dark: '#ffa600', - light: '#ffa600', - hcDark: '#ffa600', - hcLight: '#ffa600', - }, - localize( - 'mergeEditor.conflictBorder.unhandledFocused', - 'The border color of unhandled focused conflicts.' - ) + 'mergeEditor.conflict.unhandledFocused.border', + { dark: '#ffa600', light: '#ffa600', hcDark: '#ffa600', hcLight: '#ffa600', }, + localize('mergeEditor.conflict.unhandledFocused.border', 'The border color of unhandled focused conflicts.') ); export const conflictBorderHandledUnfocused = registerColor( - 'mergeEditor.conflictBorder.handledUnfocused', - { - dark: '#86868649', - light: '#86868649', - hcDark: '#86868649', - hcLight: '#86868649', - }, - localize( - 'mergeEditor.conflictBorder.handledUnfocused', - 'The border color of handled unfocused conflicts.' - ) + 'mergeEditor.conflict.handledUnfocused.border', + { dark: '#86868649', light: '#86868649', hcDark: '#86868649', hcLight: '#86868649', }, + localize('mergeEditor.conflict.handledUnfocused.border', 'The border color of handled unfocused conflicts.') ); export const conflictBorderHandledFocused = registerColor( - 'mergeEditor.conflictBorder.handledFocused', - { - dark: '#c1c1c1cc', - light: '#c1c1c1cc', - hcDark: '#c1c1c1cc', - hcLight: '#c1c1c1cc', - }, - localize( - 'mergeEditor.conflictBorder.handledFocused', - 'The border color of handled focused conflicts.' - ) + 'mergeEditor.conflict.handledFocused.border', + { dark: '#c1c1c1cc', light: '#c1c1c1cc', hcDark: '#c1c1c1cc', hcLight: '#c1c1c1cc', }, + localize('mergeEditor.conflict.handledFocused.border', 'The border color of handled focused conflicts.') ); export const handledConflictMinimapOverViewRulerColor = registerColor( - 'mergeEditor.conflict-handled.minimapOverViewRuler', - { - dark: '#adaca8ee', - light: '#adaca8ee', - hcDark: '#adaca8ee', - hcLight: '#adaca8ee', - }, - localize( - 'mergeEditor.conflict-unhandled.minimapOverViewRuler', - 'The foreground color for changes in input 1.' - ) + 'mergeEditor.conflict.handled.minimapOverViewRuler', + { dark: '#adaca8ee', light: '#adaca8ee', hcDark: '#adaca8ee', hcLight: '#adaca8ee', }, + localize('mergeEditor.conflict.handled.minimapOverViewRuler', 'The foreground color for changes in input 1.') ); export const unhandledConflictMinimapOverViewRulerColor = registerColor( - 'mergeEditor.conflict-unhandled.minimapOverViewRuler', - { - dark: '#fcba03FF', - light: '#fcba03FF', - hcDark: '#fcba03FF', - hcLight: '#fcba03FF', - }, - localize( - 'mergeEditor.conflict-unhandled.minimapOverViewRuler', - 'The foreground color for changes in input 1.' - ) + 'mergeEditor.conflict.unhandled.minimapOverViewRuler', + { dark: '#fcba03FF', light: '#fcba03FF', hcDark: '#fcba03FF', hcLight: '#fcba03FF', }, + localize('mergeEditor.conflict.unhandled.minimapOverViewRuler', 'The foreground color for changes in input 1.') ); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/media/mergeEditor.css b/src/vs/workbench/contrib/mergeEditor/browser/view/media/mergeEditor.css index 895a56245a6..b832e88bd84 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/media/mergeEditor.css +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/media/mergeEditor.css @@ -30,28 +30,28 @@ } .merge-editor-diff { - background-color: var(--vscode-mergeEditor-diff); + background-color: var(--vscode-mergeEditor-change-background); } .merge-editor-diff-word { - background-color: var(--vscode-mergeEditor-diff-word); + background-color: var(--vscode-mergeEditor-change-word-background); } .merge-editor-block { - border: 2px solid var(--vscode-mergeEditor-conflictBorder-unhandledUnfocused); + border: 2px solid var(--vscode-mergeEditor-conflict-unhandledUnfocused-border); } .merge-editor-block.focused { - border: 2px solid var(--vscode-mergeEditor-conflictBorder-unhandledFocused); + border: 2px solid var(--vscode-mergeEditor-conflict-unhandledFocused-border); } .merge-editor-block.handled { - border: 2px solid var(--vscode-mergeEditor-conflictBorder-handledUnfocused); + border: 2px solid var(--vscode-mergeEditor-conflict-handledUnfocused-border); } .merge-editor-block.handled.focused { - border: 2px solid var(--vscode-mergeEditor-conflictBorder-handledFocused); + border: 2px solid var(--vscode-mergeEditor-conflict-handledFocused-border); } .gutter-item { From 48a7f63e2c64a0ad9f194feb24b41697e871df17 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 28 Jun 2022 15:03:51 +0200 Subject: [PATCH 015/150] Fixes #153426 --- .../contrib/mergeEditor/browser/view/editorGutter.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts index fee958633e5..1b1b8f27bba 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts @@ -19,7 +19,7 @@ export class EditorGutter extends D (e) => this._editor.hasModel() ); - private readonly viewZoneChanges = new ObservableValue(0, 'counter'); + private readonly changeCounter = new ObservableValue(0, 'counter'); constructor( private readonly _editor: CodeEditorWidget, @@ -31,7 +31,11 @@ export class EditorGutter extends D this._register(autorun((reader) => this.render(reader), 'Render')); this._editor.onDidChangeViewZones(e => { - this.viewZoneChanges.set(this.viewZoneChanges.get() + 1, undefined); + this.changeCounter.set(this.changeCounter.get() + 1, undefined); + }); + + this._editor.onDidContentSizeChange(e => { + this.changeCounter.set(this.changeCounter.get() + 1, undefined); }); } @@ -41,7 +45,7 @@ export class EditorGutter extends D if (!this.modelAttached.read(reader)) { return; } - this.viewZoneChanges.read(reader); + this.changeCounter.read(reader); const scrollTop = this.scrollTop.read(reader); const visibleRanges = this._editor.getVisibleRanges(); From 42584f105ef50eba5cac85f1af5585e71ea1b302 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Tue, 28 Jun 2022 15:07:30 +0200 Subject: [PATCH 016/150] Save some time and skip installing packages that are already there (#153467) --- .github/workflows/ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 088723285cf..6f1c79762fd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -106,8 +106,6 @@ jobs: # TODO: rename azure-pipelines/linux/xvfb.init to github-actions - name: Setup Build Environment run: | - sudo apt-get update - sudo apt-get install -y libxkbfile-dev pkg-config libsecret-1-dev libxss1 dbus xvfb libgtk-3-0 libgbm1 sudo cp build/azure-pipelines/linux/xvfb.init /etc/init.d/xvfb sudo chmod +x /etc/init.d/xvfb sudo update-rc.d xvfb defaults From 67c283322188a9915d90a2cc002896bb2d655f5a Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 28 Jun 2022 15:20:27 +0200 Subject: [PATCH 017/150] report when search is finished (#153464) --- .../extensions/browser/extensionsViewlet.ts | 4 ++-- .../extensions/browser/extensionsViews.ts | 20 ++++++++++++++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index 3a96117a194..6faf5d3c10a 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -21,7 +21,7 @@ import { InstallLocalExtensionsInRemoteAction, InstallRemoteExtensionsInLocalAct import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkbenchExtensionEnablementService, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; -import { ExtensionsListView, EnabledExtensionsView, DisabledExtensionsView, RecommendedExtensionsView, WorkspaceRecommendedExtensionsView, BuiltInFeatureExtensionsView, BuiltInThemesExtensionsView, BuiltInProgrammingLanguageExtensionsView, ServerInstalledExtensionsView, DefaultRecommendedExtensionsView, UntrustedWorkspaceUnsupportedExtensionsView, UntrustedWorkspacePartiallySupportedExtensionsView, VirtualWorkspaceUnsupportedExtensionsView, VirtualWorkspacePartiallySupportedExtensionsView, DefaultPopularExtensionsView, DeprecatedExtensionsView } from 'vs/workbench/contrib/extensions/browser/extensionsViews'; +import { ExtensionsListView, EnabledExtensionsView, DisabledExtensionsView, RecommendedExtensionsView, WorkspaceRecommendedExtensionsView, BuiltInFeatureExtensionsView, BuiltInThemesExtensionsView, BuiltInProgrammingLanguageExtensionsView, ServerInstalledExtensionsView, DefaultRecommendedExtensionsView, UntrustedWorkspaceUnsupportedExtensionsView, UntrustedWorkspacePartiallySupportedExtensionsView, VirtualWorkspaceUnsupportedExtensionsView, VirtualWorkspacePartiallySupportedExtensionsView, DefaultPopularExtensionsView, DeprecatedExtensionsView, SearchMarketplaceExtensionsView } from 'vs/workbench/contrib/extensions/browser/extensionsViews'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import Severity from 'vs/base/common/severity'; @@ -268,7 +268,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio viewDescriptors.push({ id: 'workbench.views.extensions.marketplace', name: localize('marketPlace', "Marketplace"), - ctorDescriptor: new SyncDescriptor(ExtensionsListView, [{}]), + ctorDescriptor: new SyncDescriptor(SearchMarketplaceExtensionsView, [{}]), when: ContextKeyExpr.and(ContextKeyExpr.has('searchMarketplaceExtensions')), }); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index 9e8a0486ee0..a92b512f27a 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -39,7 +39,7 @@ import { IListContextMenuEvent } from 'vs/base/browser/ui/list/list'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IAction, Action, Separator, ActionRunner } from 'vs/base/common/actions'; import { ExtensionIdentifier, ExtensionUntrustedWorkspaceSupportType, ExtensionVirtualWorkspaceSupportType, IExtensionDescription, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions'; -import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; +import { CancelablePromise, createCancelablePromise, ThrottledDelayer } from 'vs/base/common/async'; import { IProductService } from 'vs/platform/product/common/productService'; import { SeverityIcon } from 'vs/platform/severityIcon/common/severityIcon'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -1202,6 +1202,24 @@ export class DeprecatedExtensionsView extends ExtensionsListView { } } +export class SearchMarketplaceExtensionsView extends ExtensionsListView { + + private readonly reportSearchFinishedDelayer = this._register(new ThrottledDelayer(2000)); + private searchWaitPromise: Promise = Promise.resolve(); + + override async show(query: string): Promise> { + const queryPromise = super.show(query); + this.reportSearchFinishedDelayer.trigger(() => this.reportSearchFinished()); + this.searchWaitPromise = queryPromise.then(null, null); + return queryPromise; + } + + private async reportSearchFinished(): Promise { + await this.searchWaitPromise; + this.telemetryService.publicLog2('extensionsView:MarketplaceSearchFinished'); + } +} + export class DefaultRecommendedExtensionsView extends ExtensionsListView { private readonly recommendedExtensionsQuery = '@recommended:all'; From 5ab9f868e3fa3d573b0e44456afd34695af56978 Mon Sep 17 00:00:00 2001 From: Max Belsky Date: Tue, 28 Jun 2022 15:36:23 +0200 Subject: [PATCH 018/150] Support `Transform to Kebab Case` (#147956) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🍡 Support `Transform to Kebab Case` * Fix eslint warnings Co-authored-by: Alexandru Dima --- .../browser/linesOperations.ts | 47 ++++++++++++ .../test/browser/linesOperations.test.ts | 76 ++++++++++++++++++- 2 files changed, 122 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts b/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts index 89493d5578e..ce0ebbc54af 100644 --- a/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts +++ b/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts @@ -1162,6 +1162,49 @@ export class SnakeCaseAction extends AbstractCaseAction { } } +export class KebabCaseAction extends AbstractCaseAction { + + public static isSupported(): boolean { + const areAllRegexpsSupported = [ + this.caseBoundary, + this.singleLetters, + this.underscoreBoundary, + ].every((regexp) => regexp.isSupported()); + + return areAllRegexpsSupported; + } + + private static caseBoundary = new BackwardsCompatibleRegExp('(\\p{Ll})(\\p{Lu})', 'gmu'); + private static singleLetters = new BackwardsCompatibleRegExp('(\\p{Lu}|\\p{N})(\\p{Lu}\\p{Ll})', 'gmu'); + private static underscoreBoundary = new BackwardsCompatibleRegExp('(\\S)(_)(\\S)', 'gm'); + + constructor() { + super({ + id: 'editor.action.transformToKebabcase', + label: nls.localize('editor.transformToKebabcase', 'Transform to Kebab Case'), + alias: 'Transform to Kebab Case', + precondition: EditorContextKeys.writable + }); + } + + protected _modifyText(text: string, _: string): string { + const caseBoundary = KebabCaseAction.caseBoundary.get(); + const singleLetters = KebabCaseAction.singleLetters.get(); + const underscoreBoundary = KebabCaseAction.underscoreBoundary.get(); + + if (!caseBoundary || !singleLetters || !underscoreBoundary) { + // one or more regexps aren't supported + return text; + } + + return text + .replace(underscoreBoundary, '$1-$3') + .replace(caseBoundary, '$1-$2') + .replace(singleLetters, '$1-$2') + .toLocaleLowerCase(); + } +} + registerEditorAction(CopyLinesUpAction); registerEditorAction(CopyLinesDownAction); registerEditorAction(DuplicateSelectionAction); @@ -1189,3 +1232,7 @@ if (SnakeCaseAction.caseBoundary.isSupported() && SnakeCaseAction.singleLetters. if (TitleCaseAction.titleBoundary.isSupported()) { registerEditorAction(TitleCaseAction); } + +if (KebabCaseAction.isSupported()) { + registerEditorAction(KebabCaseAction); +} diff --git a/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts b/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts index c8a2cf809c8..755e6c657a7 100644 --- a/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts +++ b/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts @@ -11,7 +11,7 @@ import { Selection } from 'vs/editor/common/core/selection'; import { Handler } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; -import { DeleteAllLeftAction, DeleteAllRightAction, DeleteDuplicateLinesAction, DeleteLinesAction, IndentLinesAction, InsertLineAfterAction, InsertLineBeforeAction, JoinLinesAction, LowerCaseAction, SnakeCaseAction, SortLinesAscendingAction, SortLinesDescendingAction, TitleCaseAction, TransposeAction, UpperCaseAction } from 'vs/editor/contrib/linesOperations/browser/linesOperations'; +import { DeleteAllLeftAction, DeleteAllRightAction, DeleteDuplicateLinesAction, DeleteLinesAction, IndentLinesAction, InsertLineAfterAction, InsertLineBeforeAction, JoinLinesAction, KebabCaseAction, LowerCaseAction, SnakeCaseAction, SortLinesAscendingAction, SortLinesDescendingAction, TitleCaseAction, TransposeAction, UpperCaseAction } from 'vs/editor/contrib/linesOperations/browser/linesOperations'; import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { createTextModel } from 'vs/editor/test/common/testTextModel'; @@ -814,6 +814,80 @@ suite('Editor Contrib - Line Operations', () => { assertSelection(editor, new Selection(2, 2, 2, 2)); } ); + + withTestCodeEditor( + [ + 'hello world', + 'öçşğü', + 'parseHTMLString', + 'getElementById', + 'PascalCase', + 'öçşÖÇŞğüĞÜ', + 'audioConverter.convertM4AToMP3();', + 'Capital_Snake_Case', + 'parseHTML4String', + '_accessor: ServicesAccessor', + 'Kebab-Case', + ], {}, (editor) => { + const model = editor.getModel()!; + const kebabCaseAction = new KebabCaseAction(); + + editor.setSelection(new Selection(1, 1, 1, 12)); + executeAction(kebabCaseAction, editor); + assert.strictEqual(model.getLineContent(1), 'hello world'); + assertSelection(editor, new Selection(1, 1, 1, 12)); + + editor.setSelection(new Selection(2, 1, 2, 6)); + executeAction(kebabCaseAction, editor); + assert.strictEqual(model.getLineContent(2), 'öçşğü'); + assertSelection(editor, new Selection(2, 1, 2, 6)); + + editor.setSelection(new Selection(3, 1, 3, 16)); + executeAction(kebabCaseAction, editor); + assert.strictEqual(model.getLineContent(3), 'parse-html-string'); + assertSelection(editor, new Selection(3, 1, 3, 18)); + + editor.setSelection(new Selection(4, 1, 4, 15)); + executeAction(kebabCaseAction, editor); + assert.strictEqual(model.getLineContent(4), 'get-element-by-id'); + assertSelection(editor, new Selection(4, 1, 4, 18)); + + editor.setSelection(new Selection(5, 1, 5, 11)); + executeAction(kebabCaseAction, editor); + assert.strictEqual(model.getLineContent(5), 'pascal-case'); + assertSelection(editor, new Selection(5, 1, 5, 12)); + + editor.setSelection(new Selection(6, 1, 6, 11)); + executeAction(kebabCaseAction, editor); + assert.strictEqual(model.getLineContent(6), 'öçş-öç-şğü-ğü'); + assertSelection(editor, new Selection(6, 1, 6, 14)); + + editor.setSelection(new Selection(7, 1, 7, 34)); + executeAction(kebabCaseAction, editor); + assert.strictEqual(model.getLineContent(7), 'audio-converter.convert-m4a-to-mp3();'); + assertSelection(editor, new Selection(7, 1, 7, 38)); + + editor.setSelection(new Selection(8, 1, 8, 19)); + executeAction(kebabCaseAction, editor); + assert.strictEqual(model.getLineContent(8), 'capital-snake-case'); + assertSelection(editor, new Selection(8, 1, 8, 19)); + + editor.setSelection(new Selection(9, 1, 9, 17)); + executeAction(kebabCaseAction, editor); + assert.strictEqual(model.getLineContent(9), 'parse-html4-string'); + assertSelection(editor, new Selection(9, 1, 9, 19)); + + editor.setSelection(new Selection(10, 1, 10, 28)); + executeAction(kebabCaseAction, editor); + assert.strictEqual(model.getLineContent(10), '_accessor: services-accessor'); + assertSelection(editor, new Selection(10, 1, 10, 29)); + + editor.setSelection(new Selection(11, 1, 11, 11)); + executeAction(kebabCaseAction, editor); + assert.strictEqual(model.getLineContent(11), 'kebab-case'); + assertSelection(editor, new Selection(11, 1, 11, 11)); + } + ); }); suite('DeleteAllRightAction', () => { From ef560c2604be857237b5e54386c765c19b43ceae Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Tue, 28 Jun 2022 10:09:10 -0400 Subject: [PATCH 019/150] Revert #151184 (#153482) --- src/vs/workbench/contrib/watermark/browser/media/watermark.css | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/vs/workbench/contrib/watermark/browser/media/watermark.css b/src/vs/workbench/contrib/watermark/browser/media/watermark.css index 29eba5b8a5f..2c45ebd4b16 100644 --- a/src/vs/workbench/contrib/watermark/browser/media/watermark.css +++ b/src/vs/workbench/contrib/watermark/browser/media/watermark.css @@ -23,8 +23,6 @@ text-align: center; white-space: nowrap; overflow: hidden; - /* Watermark should show even over opaque backgrounds */ - z-index: 1000; } .monaco-workbench .part.editor > .content.empty > .watermark > .watermark-box { From edc585edf6ec34d121196a3eda229c1c0fed676e Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Tue, 28 Jun 2022 16:22:45 +0200 Subject: [PATCH 020/150] Save some time and skip installing packages that are already there only in the Basic Checks build (#153487) This reverts commit 42584f105ef50eba5cac85f1af5585e71ea1b302. --- .github/workflows/basic.yml | 2 -- .github/workflows/ci.yml | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/basic.yml b/.github/workflows/basic.yml index b5fe086de05..97daf8fd946 100644 --- a/.github/workflows/basic.yml +++ b/.github/workflows/basic.yml @@ -22,8 +22,6 @@ jobs: # TODO: rename azure-pipelines/linux/xvfb.init to github-actions - name: Setup Build Environment run: | - sudo apt-get update - sudo apt-get install -y libxkbfile-dev pkg-config libsecret-1-dev libxss1 dbus xvfb libgtk-3-0 libgbm1 sudo cp build/azure-pipelines/linux/xvfb.init /etc/init.d/xvfb sudo chmod +x /etc/init.d/xvfb sudo update-rc.d xvfb defaults diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f1c79762fd..088723285cf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -106,6 +106,8 @@ jobs: # TODO: rename azure-pipelines/linux/xvfb.init to github-actions - name: Setup Build Environment run: | + sudo apt-get update + sudo apt-get install -y libxkbfile-dev pkg-config libsecret-1-dev libxss1 dbus xvfb libgtk-3-0 libgbm1 sudo cp build/azure-pipelines/linux/xvfb.init /etc/init.d/xvfb sudo chmod +x /etc/init.d/xvfb sudo update-rc.d xvfb defaults From 06143be41dd2e70b809a416298f42ab918efe0ae Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 28 Jun 2022 09:16:31 -0700 Subject: [PATCH 021/150] Polish command actions messages Fixes #153450 --- .../workbench/contrib/terminal/browser/xterm/decorationAddon.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts index 6154b78f928..fb782db6f5d 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts @@ -317,7 +317,7 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { return; } this._hoverDelayer.trigger(() => { - let hoverContent = `${localize('terminalPromptContextMenu', "Show Command Actions")}...`; + let hoverContent = `${localize('terminalPromptContextMenu', "Show Command Actions")}`; hoverContent += '\n\n---\n\n'; if (command.genericMarkProperties?.hoverMessage) { // TODO:@meganrogge localize From 3134110ec77a0741038497dedc1485497a93ab4c Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 28 Jun 2022 09:21:15 -0700 Subject: [PATCH 022/150] Extend IContextKeyChangeEvent as a better solution for #152775 (#153402) --- .../contextkey/browser/contextKeyService.ts | 13 ++++++++++--- src/vs/platform/contextkey/common/contextkey.ts | 1 + 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/vs/platform/contextkey/browser/contextKeyService.ts b/src/vs/platform/contextkey/browser/contextKeyService.ts index 02a9d6fc848..07a136ef7b9 100644 --- a/src/vs/platform/contextkey/browser/contextKeyService.ts +++ b/src/vs/platform/contextkey/browser/contextKeyService.ts @@ -225,6 +225,9 @@ class SimpleContextKeyChangeEvent implements IContextKeyChangeEvent { affectsSome(keys: IReadableSet): boolean { return keys.has(this.key); } + allKeysContainedIn(keys: IReadableSet): boolean { + return this.affectsSome(keys); + } } class ArrayContextKeyChangeEvent implements IContextKeyChangeEvent { @@ -237,6 +240,9 @@ class ArrayContextKeyChangeEvent implements IContextKeyChangeEvent { } return false; } + allKeysContainedIn(keys: IReadableSet): boolean { + return this.keys.every(key => keys.has(key)); + } } class CompositeContextKeyChangeEvent implements IContextKeyChangeEvent { @@ -249,12 +255,13 @@ class CompositeContextKeyChangeEvent implements IContextKeyChangeEvent { } return false; } + allKeysContainedIn(keys: IReadableSet): boolean { + return this.events.every(evt => evt.allKeysContainedIn(keys)); + } } function allEventKeysInContext(event: IContextKeyChangeEvent, context: Record): boolean { - return (event instanceof ArrayContextKeyChangeEvent && event.keys.every(key => key in context)) || - (event instanceof SimpleContextKeyChangeEvent && event.key in context) || - (event instanceof CompositeContextKeyChangeEvent && event.events.every(e => allEventKeysInContext(e, context))); + return event.allKeysContainedIn(new Set(Object.keys(context))); } export abstract class AbstractContextKeyService implements IContextKeyService { diff --git a/src/vs/platform/contextkey/common/contextkey.ts b/src/vs/platform/contextkey/common/contextkey.ts index 1ce10689a3a..c55d029b8e8 100644 --- a/src/vs/platform/contextkey/common/contextkey.ts +++ b/src/vs/platform/contextkey/common/contextkey.ts @@ -1597,6 +1597,7 @@ export interface IReadableSet { export interface IContextKeyChangeEvent { affectsSome(keys: IReadableSet): boolean; + allKeysContainedIn(keys: IReadableSet): boolean; } export interface IContextKeyService { From 729a499d8d46fe775ac97639ae4eb30228da21f8 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 28 Jun 2022 18:23:18 +0200 Subject: [PATCH 023/150] QuickPick - fix sorting when label contains an icon (#153475) * Fix label sorting when the label contains icons * Rename property --- src/vs/base/parts/quickinput/browser/quickInputList.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/base/parts/quickinput/browser/quickInputList.ts b/src/vs/base/parts/quickinput/browser/quickInputList.ts index bdcb5114917..2ada8ddcc60 100644 --- a/src/vs/base/parts/quickinput/browser/quickInputList.ts +++ b/src/vs/base/parts/quickinput/browser/quickInputList.ts @@ -35,6 +35,7 @@ interface IListElement { readonly index: number; readonly item: IQuickPickItem; readonly saneLabel: string; + readonly saneSortLabel: string; readonly saneMeta?: string; readonly saneAriaLabel: string; readonly saneDescription?: string; @@ -52,6 +53,7 @@ class ListElement implements IListElement, IDisposable { index!: number; item!: IQuickPickItem; saneLabel!: string; + saneSortLabel!: string; saneMeta!: string; saneAriaLabel!: string; saneDescription?: string; @@ -440,6 +442,7 @@ export class QuickInputList { if (item.type !== 'separator') { const previous = index && inputElements[index - 1]; const saneLabel = item.label && item.label.replace(/\r?\n/g, ' '); + const saneSortLabel = parseLabelWithIcons(saneLabel).text.trim(); const saneMeta = item.meta && item.meta.replace(/\r?\n/g, ' '); const saneDescription = item.description && item.description.replace(/\r?\n/g, ' '); const saneDetail = item.detail && item.detail.replace(/\r?\n/g, ' '); @@ -454,6 +457,7 @@ export class QuickInputList { index, item, saneLabel, + saneSortLabel, saneMeta, saneAriaLabel, saneDescription, @@ -738,7 +742,7 @@ function compareEntries(elementA: ListElement, elementB: ListElement, lookFor: s return 0; } - return compareAnything(elementA.saneLabel, elementB.saneLabel, lookFor); + return compareAnything(elementA.saneSortLabel, elementB.saneSortLabel, lookFor); } class QuickInputAccessibilityProvider implements IListAccessibilityProvider { From 165faf8e5333ae8ac2b0f881f2494ffe282d28e6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 28 Jun 2022 09:36:10 -0700 Subject: [PATCH 024/150] Polish shell integration failure hover Fixes #153440 --- .../contrib/terminal/browser/terminalInstance.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 4401c00feeb..b7c312ab859 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -85,6 +85,7 @@ import { IPreferencesService } from 'vs/workbench/services/preferences/common/pr import type { IMarker, ITerminalAddon, Terminal as XTermTerminal } from 'xterm'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IGenericMarkProperties } from 'vs/platform/terminal/common/terminalProcess'; +import { ICommandService } from 'vs/platform/commands/common/commands'; const enum Constants { /** @@ -378,7 +379,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { @IWorkspaceTrustRequestService private readonly _workspaceTrustRequestService: IWorkspaceTrustRequestService, @IHistoryService private readonly _historyService: IHistoryService, @ITelemetryService private readonly _telemetryService: ITelemetryService, - @IOpenerService private readonly _openerService: IOpenerService + @IOpenerService private readonly _openerService: IOpenerService, + @ICommandService private readonly _commandService: ICommandService ) { super(); @@ -1729,13 +1731,19 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { id: TerminalStatus.ShellIntegrationAttentionNeeded, severity: Severity.Warning, icon: Codicon.warning, - tooltip: (`${exitMessage} ` ?? '') + nls.localize('launchFailed.exitCodeOnlyShellIntegration', 'Disabling shell integration with {0} might help.', '`terminal.integrated.shellIntegration.enabled`'), + tooltip: (`${exitMessage} ` ?? '') + nls.localize('launchFailed.exitCodeOnlyShellIntegration', 'Disabling shell integration in user settings might help.'), hoverActions: [{ commandId: TerminalCommandId.ShellIntegrationLearnMore, - label: nls.localize('shellIntegration.learnMore', "Learn more"), + label: nls.localize('shellIntegration.learnMore', "Learn more about shell integration"), run: () => { this._openerService.open('https://code.visualstudio.com/docs/editor/integrated-terminal#_shell-integration'); } + }, { + commandId: 'workbench.action.openSettings', + label: nls.localize('shellIntegration.openSettings', "Open user settings"), + run: () => { + this._commandService.executeCommand('workbench.action.openSettings', 'terminal.integrated.shellIntegration.enabled'); + } }] }); this._telemetryService.publicLog2<{}, { owner: 'meganrogge'; comment: 'Indicates the process exited when created with shell integration args' }>('terminal/shellIntegrationFailureProcessExit'); From 496aaaf969359770addad6f19e0c32d13ec9aff2 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 28 Jun 2022 09:58:22 -0700 Subject: [PATCH 025/150] Don't export VSCODE_SHELL_INTEGRATION Fixes #153454 --- .../contrib/terminal/browser/media/shellIntegration-rc.zsh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh index d1d42db9451..a94e7c11c71 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh +++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh @@ -11,7 +11,7 @@ fi # This variable allows the shell to both detect that VS Code's shell integration is enabled as well # as disable it by unsetting the variable. -export VSCODE_SHELL_INTEGRATION=1 +VSCODE_SHELL_INTEGRATION=1 # Only fix up ZDOTDIR if shell integration was injected (not manually installed) and has not been called yet if [[ "$VSCODE_INJECTION" == "1" ]]; then From 8d23eee265e8e42b44f9b9f8f574ba50c873c96b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 28 Jun 2022 10:15:09 -0700 Subject: [PATCH 026/150] Fix OSC 633 P format in tasks Fixes #153451 --- src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index 6d56f58a027..5fef7f31d8e 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -56,7 +56,7 @@ import { GroupKind } from 'vs/workbench/contrib/tasks/common/taskConfiguration'; import { Codicon } from 'vs/base/common/codicons'; import { VSCodeOscProperty, VSCodeOscPt, VSCodeSequence } from 'vs/workbench/contrib/terminal/browser/terminalEscapeSequences'; -const taskShellIntegrationStartSequence = VSCodeSequence(VSCodeOscPt.PromptStart) + VSCodeSequence(VSCodeOscPt.Property, VSCodeOscProperty.Task) + VSCodeSequence(VSCodeOscPt.CommandStart); +const taskShellIntegrationStartSequence = VSCodeSequence(VSCodeOscPt.PromptStart) + VSCodeSequence(VSCodeOscPt.Property, `${VSCodeOscProperty.Task}=True`) + VSCodeSequence(VSCodeOscPt.CommandStart); const taskShellIntegrationOutputSequence = VSCodeSequence(VSCodeOscPt.CommandExecuted); interface ITerminalData { From 6f902cde5b49dadbb9ccf136d8b7258740edaff3 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 28 Jun 2022 10:18:01 -0700 Subject: [PATCH 027/150] Revert "Fixes #152785" This ended up causing an issue where terminals could not be destroyed. This reverts commit 8beecf197966c6fe1ee94caf0de98064098933ab. --- .../contrib/terminal/browser/terminalInstance.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 9627d4523f1..402e8cf18e8 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -1312,11 +1312,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { override dispose(immediate?: boolean): void { - if (this._isDisposed) { - return; - } - this._isDisposed = true; - this._logService.trace(`terminalInstance#dispose (instanceId: ${this.instanceId})`); dispose(this._linkManager); this._linkManager = undefined; @@ -1355,8 +1350,10 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // hasn't happened yet this._onProcessExit(undefined); - this._onDisposed.fire(this); - + if (!this._isDisposed) { + this._isDisposed = true; + this._onDisposed.fire(this); + } super.dispose(); } From 916360c367758511b5e651b79152ba5077f171c4 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 28 Jun 2022 19:09:47 +0200 Subject: [PATCH 028/150] Disable code lens and gutter for input 1 and 2. --- .../contrib/mergeEditor/browser/utils.ts | 16 +++++++++++ .../browser/view/editors/codeEditorView.ts | 28 +++++++++---------- .../view/editors/inputCodeEditorView.ts | 15 ++++++---- .../view/editors/resultCodeEditorView.ts | 5 ++-- .../mergeEditor/browser/view/mergeEditor.ts | 21 +++++++++----- 5 files changed, 55 insertions(+), 30 deletions(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/utils.ts b/src/vs/workbench/contrib/mergeEditor/browser/utils.ts index 308b6ab01ff..23277aeae95 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/utils.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/utils.ts @@ -143,3 +143,19 @@ export function thenIfNotDisposed(promise: Promise, then: () => void): IDi export function setFields(obj: T, fields: Partial): T { return Object.assign(obj, fields); } + +export function deepMerge(source1: T, source2: Partial): T { + const result = {} as T; + for (const key in source1) { + result[key] = source1[key]; + } + for (const key in source2) { + const source2Value = source2[key]; + if (typeof result[key] === 'object' && source2Value && typeof source2Value === 'object') { + result[key] = deepMerge(result[key], source2Value); + } else { + result[key] = source2Value as any; + } + } + return result; +} diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts index a5c8bf676d5..e9a1b888ce0 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts @@ -8,7 +8,9 @@ import { IView, IViewSize } from 'vs/base/browser/ui/grid/grid'; import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; +import { IEditorContributionDescription } from 'vs/editor/browser/editorExtensions'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { ITextModel } from 'vs/editor/common/model'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { DEFAULT_EDITOR_MAX_DIMENSIONS, DEFAULT_EDITOR_MIN_DIMENSIONS } from 'vs/workbench/browser/parts/editor/editor'; @@ -16,10 +18,6 @@ import { IObservable, observableFromEvent, ObservableValue } from 'vs/workbench/ import { setStyle } from 'vs/workbench/contrib/mergeEditor/browser/utils'; import { MergeEditorViewModel } from 'vs/workbench/contrib/mergeEditor/browser/view/viewModel'; -export interface ICodeEditorViewOptions { - readonly: boolean; -} - export abstract class CodeEditorView extends Disposable { private readonly _viewModel = new ObservableValue(undefined, 'viewModel'); readonly viewModel: IObservable = this._viewModel; @@ -61,15 +59,16 @@ export abstract class CodeEditorView extends Disposable { public readonly editor = this.instantiationService.createInstance( CodeEditorWidget, this.htmlElements.editor, + {}, { - minimap: { enabled: false }, - readOnly: this._options.readonly, - glyphMargin: false, - lineNumbersMinChars: 2, - }, - {} + contributions: this.getEditorContributions(), + } ); + public updateOptions(newOptions: Readonly): void { + this.editor.updateOptions(newOptions); + } + public readonly isFocused = observableFromEvent( Event.any(this.editor.onDidBlurEditorWidget, this.editor.onDidFocusEditorWidget), () => this.editor.hasWidgetFocus() @@ -82,18 +81,17 @@ export abstract class CodeEditorView extends Disposable { public readonly cursorLineNumber = this.cursorPosition.map(p => p?.lineNumber); - /*derivedObservable('cursorLineNumber', reader => { - return this.cursorPosition.read(reader)?.lineNumber; - });*/ - constructor( - private readonly _options: ICodeEditorViewOptions, @IInstantiationService private readonly instantiationService: IInstantiationService ) { super(); } + protected getEditorContributions(): IEditorContributionDescription[] | undefined { + return undefined; + } + public setModel( viewModel: MergeEditorViewModel, textModel: ITextModel, diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts index d7d2bb31291..8e974f8c13e 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts @@ -3,12 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as dom from 'vs/base/browser/dom'; import { Toggle } from 'vs/base/browser/ui/toggle/toggle'; import { Action, IAction } from 'vs/base/common/actions'; import { Codicon } from 'vs/base/common/codicons'; import { Disposable } from 'vs/base/common/lifecycle'; import { noBreakWhitespace } from 'vs/base/common/strings'; +import { isDefined } from 'vs/base/common/types'; +import { EditorExtensionsRegistry, IEditorContributionDescription } from 'vs/editor/browser/editorExtensions'; import { IModelDeltaDecoration, MinimapPosition, OverviewRulerLane } from 'vs/editor/common/model'; +import { CodeLensContribution } from 'vs/editor/contrib/codelens/browser/codelensController'; import { localize } from 'vs/nls'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -17,9 +21,7 @@ import { InputState, ModifiedBaseRangeState } from 'vs/workbench/contrib/mergeEd import { applyObservableDecorations, setFields } from 'vs/workbench/contrib/mergeEditor/browser/utils'; import { handledConflictMinimapOverViewRulerColor, unhandledConflictMinimapOverViewRulerColor } from 'vs/workbench/contrib/mergeEditor/browser/view/colors'; import { EditorGutter, IGutterItemInfo, IGutterItemView } from '../editorGutter'; -import { CodeEditorView, ICodeEditorViewOptions } from './codeEditorView'; -import * as dom from 'vs/base/browser/dom'; -import { isDefined } from 'vs/base/common/types'; +import { CodeEditorView } from './codeEditorView'; export class InputCodeEditorView extends CodeEditorView { private readonly decorations = derivedObservable('decorations', reader => { @@ -97,11 +99,10 @@ export class InputCodeEditorView extends CodeEditorView { constructor( public readonly inputNumber: 1 | 2, - options: ICodeEditorViewOptions, @IInstantiationService instantiationService: IInstantiationService, @IContextMenuService contextMenuService: IContextMenuService, ) { - super(options, instantiationService); + super(instantiationService); this._register(applyObservableDecorations(this.editor, this.decorations)); @@ -210,6 +211,10 @@ export class InputCodeEditorView extends CodeEditorView { }) ); } + + protected override getEditorContributions(): IEditorContributionDescription[] | undefined { + return EditorExtensionsRegistry.getEditorContributions().filter(c => c.id !== CodeLensContribution.ID); + } } export interface ModifiedBaseRangeGutterItemInfo extends IGutterItemInfo { diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts index 2037af7fa49..1bdd79fb0e1 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts @@ -10,7 +10,7 @@ import { autorun, derivedObservable } from 'vs/workbench/contrib/audioCues/brows import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange'; import { applyObservableDecorations, join } from 'vs/workbench/contrib/mergeEditor/browser/utils'; import { handledConflictMinimapOverViewRulerColor, unhandledConflictMinimapOverViewRulerColor } from 'vs/workbench/contrib/mergeEditor/browser/view/colors'; -import { CodeEditorView, ICodeEditorViewOptions } from './codeEditorView'; +import { CodeEditorView } from './codeEditorView'; export class ResultCodeEditorView extends CodeEditorView { private readonly decorations = derivedObservable('decorations', reader => { @@ -99,10 +99,9 @@ export class ResultCodeEditorView extends CodeEditorView { }); constructor( - options: ICodeEditorViewOptions, @IInstantiationService instantiationService: IInstantiationService ) { - super(options, instantiationService); + super(instantiationService); this._register(applyObservableDecorations(this.editor, this.decorations)); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts index 6c618b81d56..2e89cacbabd 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts @@ -40,7 +40,7 @@ import { autorunWithStore, IObservable } from 'vs/workbench/contrib/audioCues/br import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; import { DocumentMapping, getOppositeDirection, MappingDirection } from 'vs/workbench/contrib/mergeEditor/browser/model/mapping'; import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel'; -import { ReentrancyBarrier, thenIfNotDisposed } from 'vs/workbench/contrib/mergeEditor/browser/utils'; +import { deepMerge, ReentrancyBarrier, thenIfNotDisposed } from 'vs/workbench/contrib/mergeEditor/browser/utils'; import { MergeEditorViewModel } from 'vs/workbench/contrib/mergeEditor/browser/view/viewModel'; import { settingsSashBorder } from 'vs/workbench/contrib/preferences/common/settingsEditorColorRegistry'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -92,9 +92,9 @@ export class MergeEditor extends AbstractTextEditor { private _grid!: Grid; - private readonly input1View = this._register(this.instantiation.createInstance(InputCodeEditorView, 1, { readonly: !this.inputsWritable })); - private readonly input2View = this._register(this.instantiation.createInstance(InputCodeEditorView, 2, { readonly: !this.inputsWritable })); - private readonly inputResultView = this._register(this.instantiation.createInstance(ResultCodeEditorView, { readonly: false })); + private readonly input1View = this._register(this.instantiation.createInstance(InputCodeEditorView, 1)); + private readonly input2View = this._register(this.instantiation.createInstance(InputCodeEditorView, 2)); + private readonly inputResultView = this._register(this.instantiation.createInstance(ResultCodeEditorView)); private readonly _layoutMode: MergeEditorLayout; private readonly _ctxIsMergeEditor: IContextKey; @@ -250,9 +250,16 @@ export class MergeEditor extends AbstractTextEditor { } private applyOptions(options: ICodeEditorOptions): void { - this.input1View.editor.updateOptions({ ...options, readOnly: !this.inputsWritable }); - this.input2View.editor.updateOptions({ ...options, readOnly: !this.inputsWritable }); - this.inputResultView.editor.updateOptions(options); + const inputOptions: ICodeEditorOptions = deepMerge(options, { + minimap: { enabled: false }, + glyphMargin: false, + lineNumbersMinChars: 2, + readOnly: !this.inputsWritable + }); + + this.input1View.updateOptions(inputOptions); + this.input2View.updateOptions(inputOptions); + this.inputResultView.updateOptions(options); } protected getMainControl(): ICodeEditor | undefined { From 62bd8e457c3465343104cca71cb1651191f9f476 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 28 Jun 2022 19:59:08 +0200 Subject: [PATCH 029/150] Fixes #151127 --- src/vs/base/browser/dom.ts | 38 ++++++++++++++++--- .../mergeEditor/browser/view/editorGutter.ts | 13 ++++++- 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index edb106eb05d..4d08566a4f6 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -1734,6 +1734,19 @@ export function computeClippingRect(elementOrRect: HTMLElement | DOMRectReadOnly return { top, right, bottom, left }; } +interface DomNodeAttributes { + role?: string; + ariaHidden?: boolean; + style?: StyleAttributes; +} + +interface StyleAttributes { + height?: number | string; + width?: number | string; +} + +// + /** * A helper function to create nested dom nodes. * @@ -1749,22 +1762,22 @@ export function computeClippingRect(elementOrRect: HTMLElement | DOMRectReadOnly * private readonly editor = createEditor(this.htmlElements.editor); * ``` */ -export function h(tag: TTag): never; export function h( tag: TTag, - attributes: { $: TId } + attributes: { $: TId } & DomNodeAttributes ): Record>; +export function h(tag: TTag, attributes: DomNodeAttributes): Record<'root', TagToElement>; export function h)[]>( tag: TTag, children: T ): (ArrayToObj & Record<'root', TagToElement>) extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never; export function h)[]>( tag: TTag, - attributes: { $: TId }, + attributes: { $: TId } & DomNodeAttributes, children: T ): (ArrayToObj & Record>) extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never; -export function h(tag: string, ...args: [] | [attributes: { $: string } | Record, children?: any[]] | [children: any[]]): Record { - let attributes: Record; +export function h(tag: string, ...args: [] | [attributes: { $: string } & DomNodeAttributes | Record, children?: any[]] | [children: any[]]): Record { + let attributes: { $?: string } & DomNodeAttributes; let children: (Record | HTMLElement)[] | undefined; if (Array.isArray(args[0])) { @@ -1801,7 +1814,16 @@ export function h(tag: string, ...args: [] | [attributes: { $: string } | Record result[value] = el; continue; } - el.setAttribute(key, value); + if (key === 'style') { + for (const [cssKey, cssValue] of Object.entries(value)) { + el.style.setProperty( + camelCaseToHyphenCase(cssKey), + typeof cssValue === 'number' ? cssValue + 'px' : '' + cssValue + ); + } + continue; + } + el.setAttribute(camelCaseToHyphenCase(key), value.toString()); } result['root'] = el; @@ -1809,6 +1831,10 @@ export function h(tag: string, ...args: [] | [attributes: { $: string } | Record return result; } +function camelCaseToHyphenCase(str: string) { + return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); +} + type RemoveHTMLElement = T extends HTMLElement ? never : T; type ArrayToObj = UnionToIntersection>; diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts index 1b1b8f27bba..2b208650738 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { h } from 'vs/base/browser/dom'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; @@ -27,7 +28,17 @@ export class EditorGutter extends D private readonly itemProvider: IGutterItemProvider ) { super(); - this._domNode.className = 'gutter'; + this._domNode.className = 'gutter monaco-editor'; + const scrollDecoration = this._domNode.appendChild( + h('div.scroll-decoration', { role: 'presentation', ariaHidden: true, style: { width: '100%' } }) + .root + ); + + this._register(autorun((reader) => { + scrollDecoration.className = this.scrollTop.read(reader) === 0 ? '' : 'scroll-decoration'; + }, 'update scroll decoration')); + + this._register(autorun((reader) => this.render(reader), 'Render')); this._editor.onDidChangeViewZones(e => { From 2824714a96e5f5b4b0f918dce1dde85c3f66d259 Mon Sep 17 00:00:00 2001 From: SteVen Batten <6561887+sbatten@users.noreply.github.com> Date: Tue, 28 Jun 2022 11:20:00 -0700 Subject: [PATCH 030/150] fix menu bar flyout positioning (#153558) Improve menu bar flyout positioning refs #150170 --- src/vs/base/browser/ui/menu/menubar.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/vs/base/browser/ui/menu/menubar.ts b/src/vs/base/browser/ui/menu/menubar.ts index 82e5c1b811a..10dd697e7a4 100644 --- a/src/vs/base/browser/ui/menu/menubar.ts +++ b/src/vs/base/browser/ui/menu/menubar.ts @@ -979,7 +979,7 @@ export class MenuBar extends Disposable { const actualMenuIndex = menuIndex >= this.numMenusShown ? MenuBar.OVERFLOW_INDEX : menuIndex; const customMenu = actualMenuIndex === MenuBar.OVERFLOW_INDEX ? this.overflowMenu : this.menus[actualMenuIndex]; - if (!customMenu.actions || !customMenu.buttonElement) { + if (!customMenu.actions || !customMenu.buttonElement || !customMenu.titleElement) { return; } @@ -987,19 +987,19 @@ export class MenuBar extends Disposable { customMenu.buttonElement.classList.add('open'); - const buttonBoundingRect = customMenu.buttonElement.getBoundingClientRect(); - const buttonBoundingRectZoom = DOM.getDomNodeZoomLevel(customMenu.buttonElement); + const titleBoundingRect = customMenu.titleElement.getBoundingClientRect(); + const titleBoundingRectZoom = DOM.getDomNodeZoomLevel(customMenu.titleElement); if (this.options.compactMode === Direction.Right) { - menuHolder.style.top = `${buttonBoundingRect.top}px`; - menuHolder.style.left = `${buttonBoundingRect.left + this.container.clientWidth}px`; + menuHolder.style.top = `${titleBoundingRect.top}px`; + menuHolder.style.left = `${titleBoundingRect.left + this.container.clientWidth}px`; } else if (this.options.compactMode === Direction.Left) { - menuHolder.style.top = `${buttonBoundingRect.top}px`; + menuHolder.style.top = `${titleBoundingRect.top}px`; menuHolder.style.right = `${this.container.clientWidth}px`; menuHolder.style.left = 'auto'; } else { - menuHolder.style.top = `${buttonBoundingRect.bottom * buttonBoundingRectZoom}px`; - menuHolder.style.left = `${buttonBoundingRect.left * buttonBoundingRectZoom}px`; + menuHolder.style.top = `${titleBoundingRect.bottom * titleBoundingRectZoom}px`; + menuHolder.style.left = `${titleBoundingRect.left * titleBoundingRectZoom}px`; } customMenu.buttonElement.appendChild(menuHolder); From a432fbda62b429c8d547005be94f2fa27cd6f02b Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 28 Jun 2022 11:44:08 -0700 Subject: [PATCH 031/150] debug: bump js-debug for 1.69 (#153571) --- product.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/product.json b/product.json index d16abf8de29..a50c00f6cf3 100644 --- a/product.json +++ b/product.json @@ -46,7 +46,7 @@ }, { "name": "ms-vscode.js-debug", - "version": "1.68.0", + "version": "1.69.0", "repo": "https://github.com/microsoft/vscode-js-debug", "metadata": { "id": "25629058-ddac-4e17-abba-74678e126c5d", From 1ef276014c93a2499be089a4e3b9c3ca71ff6cd5 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Tue, 28 Jun 2022 21:16:50 +0200 Subject: [PATCH 032/150] Fixes #153465: Turn off `UtilityProcess` for stable (#153594) --- .../contrib/extensions/browser/extensions.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 57969a4ba9b..f64e267746c 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -228,7 +228,7 @@ Registry.as(ConfigurationExtensions.Configuration) 'extensions.experimental.useUtilityProcess': { type: 'boolean', description: localize('extensionsUseUtilityProcess', "When enabled, the extension host will be launched using the new UtilityProcess Electron API."), - default: true + default: false }, [WORKSPACE_TRUST_EXTENSION_SUPPORT]: { type: 'object', From 46956d29e648052263997c185fc046e29cadee2c Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 28 Jun 2022 15:58:25 -0400 Subject: [PATCH 033/150] show icon in `Show All` tasks quickpick (#153595) fix #153491 --- .../workbench/contrib/tasks/browser/taskQuickPick.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts b/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts index f7d122e7e15..77dc6f60769 100644 --- a/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts +++ b/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts @@ -64,7 +64,7 @@ export class TaskQuickPick extends Disposable { private _guessTaskLabel(task: Task | ConfiguringTask): string { if (task._label) { - return TaskQuickPick.getTaskLabelWithIcon(task); + return task._label; } if (ConfiguringTask.is(task)) { let label: string = task.configures.type; @@ -77,12 +77,13 @@ export class TaskQuickPick extends Disposable { return ''; } - public static getTaskLabelWithIcon(task: Task | ConfiguringTask): string { + public static getTaskLabelWithIcon(task: Task | ConfiguringTask, labelGuess?: string): string { + const label = labelGuess || task._label; const icon = task.configurationProperties.icon; if (!icon) { - return `${task._label}`; + return `${label}`; } - return icon.id ? `$(${icon.id}) ${task._label}` : `$(${Codicon.tools.id}) ${task._label}`; + return icon.id ? `$(${icon.id}) ${label}` : `$(${Codicon.tools.id}) ${label}`; } public static applyColorStyles(task: Task | ConfiguringTask, entry: TaskQuickPickEntryType | ITaskTwoLevelQuickPickEntry, themeService: IThemeService): void { @@ -95,7 +96,7 @@ export class TaskQuickPick extends Disposable { } private _createTaskEntry(task: Task | ConfiguringTask, extraButtons: IQuickInputButton[] = []): ITaskTwoLevelQuickPickEntry { - const entry: ITaskTwoLevelQuickPickEntry = { label: this._guessTaskLabel(task), description: this._taskService.getTaskDescription(task), task, detail: this._showDetail() ? task.configurationProperties.detail : undefined }; + const entry: ITaskTwoLevelQuickPickEntry = { label: TaskQuickPick.getTaskLabelWithIcon(task, this._guessTaskLabel(task)), description: this._taskService.getTaskDescription(task), task, detail: this._showDetail() ? task.configurationProperties.detail : undefined }; entry.buttons = []; entry.buttons.push({ iconClass: ThemeIcon.asClassName(configureTaskIcon), tooltip: nls.localize('configureTask', "Configure Task") }); entry.buttons.push(...extraButtons); From 2ade948dab0a8ee92c2b126dce7978cbcb5a0956 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 28 Jun 2022 15:58:37 -0400 Subject: [PATCH 034/150] add task icon description (#153596) fix #153496 --- src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts b/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts index d455def1f6e..077bd89a60e 100644 --- a/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts +++ b/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts @@ -97,15 +97,16 @@ const detail: IJSONSchema = { const icon: IJSONSchema = { type: 'object', + description: nls.localize('JsonSchema.tasks.icon', 'An optional icon for the task'), properties: { id: { - description: nls.localize('JsonSchema.tasks.icon.id', 'An optional icon for the task'), - type: 'string', + description: nls.localize('JsonSchema.tasks.icon.id', 'An optional codicon ID to use'), + type: ['string', 'null'], enum: Array.from(Codicon.getAll(), icon => icon.id), markdownEnumDescriptions: Array.from(Codicon.getAll(), icon => `$(${icon.id})`), }, color: { - description: nls.localize('JsonSchema.tasks.icon.color', 'An optional color to use for the task icon'), + description: nls.localize('JsonSchema.tasks.icon.color', 'An optional color of the icon'), type: ['string', 'null'], enum: [ 'terminal.ansiBlack', From 8fd6e38eee1ec714b9c1882fdd1c65c518f8373d Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Tue, 28 Jun 2022 13:34:20 -0700 Subject: [PATCH 035/150] Do not store empty edit sessions (#153599) * Do not store empty edit sessions * Add unit test --- .../browser/sessionSync.contribution.ts | 17 ++++++++++--- .../test/browser/sessionSync.test.ts | 25 +++++++++++++++---- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts b/src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts index 3c102115db8..d14c6922b0d 100644 --- a/src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts +++ b/src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts @@ -157,7 +157,7 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon if (uri === undefined) { return; } // Run the store action to get back a ref - const ref = await that.storeEditSession(); + const ref = await that.storeEditSession(false); // Append the ref to the URI if (ref !== undefined) { @@ -215,7 +215,7 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon await that.progressService.withProgress({ location: ProgressLocation.Notification, title: localize('storing edit session', 'Storing edit session...') - }, async () => await that.storeEditSession()); + }, async () => await that.storeEditSession(true)); } })); } @@ -293,8 +293,9 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon } } - async storeEditSession(): Promise { + async storeEditSession(fromStoreCommand: boolean): Promise { const folders: Folder[] = []; + let hasEdits = false; for (const repository of this.scmService.repositories) { // Look through all resource groups and compute which files were added/modified/deleted @@ -321,6 +322,8 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon } } catch { } + hasEdits = true; + if (await this.fileService.exists(uri)) { workingChanges.push({ type: ChangeType.Addition, fileType: FileType.File, contents: (await this.fileService.readFile(uri)).value.toString(), relativeFilePath: relativeFilePath }); } else { @@ -332,6 +335,14 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon folders.push({ workingChanges, name: name ?? '' }); } + if (!hasEdits) { + this.logService.info('Edit Sessions: Skipping storing edit session as there are no edits to store.'); + if (fromStoreCommand) { + this.notificationService.info(localize('no edits to store', 'Skipped storing edit session as there are no edits to store.')); + } + return undefined; + } + const data: EditSession = { folders, version: 1 }; try { diff --git a/src/vs/workbench/contrib/sessionSync/test/browser/sessionSync.test.ts b/src/vs/workbench/contrib/sessionSync/test/browser/sessionSync.test.ts index cdb412c5060..5c29394116b 100644 --- a/src/vs/workbench/contrib/sessionSync/test/browser/sessionSync.test.ts +++ b/src/vs/workbench/contrib/sessionSync/test/browser/sessionSync.test.ts @@ -36,10 +36,13 @@ suite('Edit session sync', () => { let instantiationService: TestInstantiationService; let sessionSyncContribution: SessionSyncContribution; let fileService: FileService; + let sandbox: sinon.SinonSandbox; const disposables = new DisposableStore(); - setup(() => { + suiteSetup(() => { + sandbox = sinon.createSandbox(); + instantiationService = new TestInstantiationService(); // Set up filesystem @@ -71,6 +74,9 @@ suite('Edit session sync', () => { } }); + // Stub repositories + instantiationService.stub(ISCMService, '_repositories', new Map()); + sessionSyncContribution = instantiationService.createInstance(SessionSyncContribution); }); @@ -100,13 +106,9 @@ suite('Edit session sync', () => { }; // Stub sync service to return edit session data - const sandbox = sinon.createSandbox(); const readStub = sandbox.stub().returns({ editSession, ref: '0' }); instantiationService.stub(ISessionSyncWorkbenchService, 'read', readStub); - // Stub repositories - instantiationService.stub(ISCMService, '_repositories', new Map()); - // Create root folder await fileService.createFolder(folderUri); @@ -116,4 +118,17 @@ suite('Edit session sync', () => { // Verify edit session was correctly applied assert.equal((await fileService.readFile(fileUri)).value.toString(), fileContents); }); + + test('Edit session not stored if there are no edits', async function () { + const writeStub = sandbox.stub(); + instantiationService.stub(ISessionSyncWorkbenchService, 'write', writeStub); + + // Create root folder + await fileService.createFolder(folderUri); + + await sessionSyncContribution.storeEditSession(true); + + // Verify that we did not attempt to write the edit session + assert.equal(writeStub.called, false); + }); }); From adecb6b529448a6ee43dfc67aa6bde65a10277c3 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 28 Jun 2022 22:46:03 +0200 Subject: [PATCH 036/150] Git - fix secondary action localization (#153604) Fix #153503 --- extensions/git/src/actionButton.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions/git/src/actionButton.ts b/extensions/git/src/actionButton.ts index f461a92ec6b..56d87027361 100644 --- a/extensions/git/src/actionButton.ts +++ b/extensions/git/src/actionButton.ts @@ -111,17 +111,17 @@ export class ActionButtonCommand { [ { command: 'git.commit', - title: 'Commit', + title: localize('scm secondary button commit', "Commit"), arguments: [this.repository.sourceControl, ''], }, { command: 'git.commit', - title: 'Commit & Push', + title: localize('scm secondary button commit and push', "Commit & Push"), arguments: [this.repository.sourceControl, 'push'], }, { command: 'git.commit', - title: 'Commit & Sync', + title: localize('scm secondary button commit and sync', "Commit & Sync"), arguments: [this.repository.sourceControl, 'sync'], }, ] From e773c97507002443cd091d4900bfb9cd917c54ff Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 28 Jun 2022 23:07:16 +0200 Subject: [PATCH 037/150] Git - commit action button does not appear on the initial commit (#153605) Fix #153507 --- extensions/git/src/actionButton.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/git/src/actionButton.ts b/extensions/git/src/actionButton.ts index 56d87027361..bfbb490d102 100644 --- a/extensions/git/src/actionButton.ts +++ b/extensions/git/src/actionButton.ts @@ -49,7 +49,7 @@ export class ActionButtonCommand { } get button(): SourceControlActionButton | undefined { - if (!this.state.HEAD || !this.state.HEAD.name || !this.state.HEAD.commit) { return undefined; } + if (!this.state.HEAD || !this.state.HEAD.name) { return undefined; } let actionButton: SourceControlActionButton | undefined; if (this.state.repositoryHasNoChanges) { From e759e03daff80d351fd9c0e96719ffd9ecc19bde Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 28 Jun 2022 17:16:01 -0400 Subject: [PATCH 038/150] polish message for watch task hover (#153606) fix #153600 --- src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts b/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts index e74c85e3f74..47d1f2a6486 100644 --- a/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts +++ b/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts @@ -59,7 +59,7 @@ export class TaskTerminalStatus extends Disposable { }); problemMatcher.onDidFindErrors(() => { if (this._marker) { - terminal.addGenericMark(this._marker, { hoverMessage: nls.localize('task.watchFirstError', "First error"), disableCommandStorage: true }); + terminal.addGenericMark(this._marker, { hoverMessage: nls.localize('task.watchFirstError', "Beginning of detected errors for this run"), disableCommandStorage: true }); } }); problemMatcher.onDidRequestInvalidateLastMarker(() => { From ce97d28cf77e5274ed33276ed81f84c61a051632 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Tue, 28 Jun 2022 15:26:13 -0700 Subject: [PATCH 039/150] Log when clearing cached edit sessions auth info (#153610) --- .../sessionSync/browser/sessionSyncWorkbenchService.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/services/sessionSync/browser/sessionSyncWorkbenchService.ts b/src/vs/workbench/services/sessionSync/browser/sessionSyncWorkbenchService.ts index f2c7a284c62..54c386724ba 100644 --- a/src/vs/workbench/services/sessionSync/browser/sessionSyncWorkbenchService.ts +++ b/src/vs/workbench/services/sessionSync/browser/sessionSyncWorkbenchService.ts @@ -140,7 +140,10 @@ export class SessionSyncWorkbenchService extends Disposable implements ISessionS if (!this.storeClient) { this.storeClient = new UserDataSyncStoreClient(URI.parse(this.serverConfiguration.url), this.productService, this.requestService, this.logService, this.environmentService, this.fileService, this.storageService); - this._register(this.storeClient.onTokenFailed(() => this.clearAuthenticationPreference())); + this._register(this.storeClient.onTokenFailed(() => { + this.logService.info('Edit Sessions: clearing edit sessions authentication preference because of successive token failures.'); + this.clearAuthenticationPreference(); + })); } // If we already have an existing auth session in memory, use that From 3184320a6c3dfa73ea216a53907ffa5bdc0cba12 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Tue, 28 Jun 2022 15:26:39 -0700 Subject: [PATCH 040/150] Notify when there are no edit sessions to apply (#153608) --- .../contrib/sessionSync/browser/sessionSync.contribution.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts b/src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts index d14c6922b0d..77932003da9 100644 --- a/src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts +++ b/src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts @@ -227,6 +227,11 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon const data = await this.sessionSyncWorkbenchService.read(ref); if (!data) { + if (ref === undefined) { + this.notificationService.info(localize('no edit session', 'There are no edit sessions to apply.')); + } else { + this.notificationService.warn(localize('no edit session content for ref', 'Could not apply edit session contents for ID {0}.', ref)); + } this.logService.info(`Edit Sessions: Aborting applying edit session as no edit session content is available to be applied from ref ${ref}.`); return; } From 34f31688b8f60390efe4bcea25cae570b59163b3 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 29 Jun 2022 00:27:53 +0200 Subject: [PATCH 041/150] reset to default profile if current profile is removed (#153609) --- .../browser/userDataProfileManagement.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts index 64fc161b5c5..0f4c910c0f1 100644 --- a/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts @@ -13,7 +13,7 @@ import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagemen import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { IFileService } from 'vs/platform/files/common/files'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { EXTENSIONS_RESOURCE_NAME, IUserDataProfile, IUserDataProfilesService, UseDefaultProfileFlags, WorkspaceIdentifier } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { DidChangeProfilesEvent, EXTENSIONS_RESOURCE_NAME, IUserDataProfile, IUserDataProfilesService, UseDefaultProfileFlags, WorkspaceIdentifier } from 'vs/platform/userDataProfile/common/userDataProfile'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IExtensionManagementServerService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; @@ -38,6 +38,7 @@ export class UserDataProfileManagementService extends Disposable implements IUse @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, ) { super(); + this._register(userDataProfilesService.onDidChangeProfiles(e => this.onDidChangeProfiles(e))); } private async checkAndCreateExtensionsProfileResource(): Promise { @@ -51,6 +52,12 @@ export class UserDataProfileManagementService extends Disposable implements IUse throw new Error('Invalid Profile'); } + private onDidChangeProfiles(e: DidChangeProfilesEvent): void { + if (e.removed.some(profile => profile.id === this.userDataProfileService.currentProfile.id)) { + this.enterProfile(this.userDataProfilesService.defaultProfile, false, localize('reload message when removed', "The current profile has been removed. Please reload to switch back to default profile")); + } + } + async createAndEnterProfile(name: string, useDefaultFlags?: UseDefaultProfileFlags, fromExisting?: boolean): Promise { const workspaceIdentifier = this.getWorkspaceIdentifier(); const promises: Promise[] = []; @@ -112,11 +119,11 @@ export class UserDataProfileManagementService extends Disposable implements IUse return 'empty-window'; } - private async enterProfile(profile: IUserDataProfile, preserveData: boolean): Promise { + private async enterProfile(profile: IUserDataProfile, preserveData: boolean, reloadMessage?: string): Promise { if (this.environmentService.remoteAuthority) { const result = await this.dialogService.confirm({ type: 'info', - message: localize('reload message', "Switching a profile requires reloading VS Code."), + message: reloadMessage ?? localize('reload message', "Switching a profile requires reloading VS Code."), primaryButton: localize('reload button', "&&Reload"), }); if (result.confirmed) { From a31d51981cc94a94da9ab75fc53927dcedaeaba0 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 28 Jun 2022 16:42:44 -0700 Subject: [PATCH 042/150] Improve perf when dragging tabs (#153623) Fixes #153618 --- src/vs/workbench/browser/parts/editor/editorDropTarget.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts index af98c5ae2c8..4d29cc8f143 100644 --- a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts +++ b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts @@ -597,7 +597,11 @@ export class EditorDropTarget extends Themable { private registerListeners(): void { this._register(addDisposableListener(this.container, EventType.DRAG_ENTER, e => this.onDragEnter(e))); this._register(addDisposableListener(this.container, EventType.DRAG_LEAVE, () => this.onDragLeave())); - this._register(addDisposableListener(this.container, EventType.DRAG_OVER, e => this.onDragEnter(e))); + this._register(addDisposableListener(this.container, EventType.DRAG_OVER, e => { + if (!this.overlay) { + this.onDragEnter(e); + } + })); [this.container, window].forEach(node => this._register(addDisposableListener(node as HTMLElement, EventType.DRAG_END, () => this.onDragEnd()))); } From 1d16e313d6343eb04c6d104115be504def2ceeba Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Tue, 28 Jun 2022 16:43:24 -0700 Subject: [PATCH 043/150] [typescript-language-features] Add setting for autoImportFileExcludePatterns (#153160) * Add setting for autoImportFileExcludePatterns * Add TS 4.8 to message --- .../typescript-language-features/package.json | 16 ++++++++++++++++ .../package.nls.json | 1 + .../languageFeatures/fileConfigurationManager.ts | 15 +++++++++++++++ 3 files changed, 32 insertions(+) diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index bd15126b55d..e3b7eadb0c4 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -983,6 +983,22 @@ "markdownDescription": "%typescript.preferences.includePackageJsonAutoImports%", "scope": "window" }, + "typescript.preferences.autoImportFileExcludePatterns": { + "type": "array", + "items": { + "type": "string" + }, + "markdownDescription": "%typescript.preferences.autoImportFileExcludePatterns%", + "scope": "resource" + }, + "javascript.preferences.autoImportFileExcludePatterns": { + "type": "array", + "items": { + "type": "string" + }, + "markdownDescription": "%typescript.preferences.autoImportFileExcludePatterns%", + "scope": "resource" + }, "javascript.preferences.renameShorthandProperties": { "type": "boolean", "default": true, diff --git a/extensions/typescript-language-features/package.nls.json b/extensions/typescript-language-features/package.nls.json index 513bcfbaaa1..03752b1f095 100644 --- a/extensions/typescript-language-features/package.nls.json +++ b/extensions/typescript-language-features/package.nls.json @@ -136,6 +136,7 @@ "typescript.preferences.includePackageJsonAutoImports.auto": "Search dependencies based on estimated performance impact.", "typescript.preferences.includePackageJsonAutoImports.on": "Always search dependencies.", "typescript.preferences.includePackageJsonAutoImports.off": "Never search dependencies.", + "typescript.preferences.autoImportFileExcludePatterns": "Specify glob patterns of files to exclude from auto imports. Requires using TypeScript 4.8 or newer in the workspace.", "typescript.updateImportsOnFileMove.enabled": "Enable/disable automatic updating of import paths when you rename or move a file in VS Code.", "typescript.updateImportsOnFileMove.enabled.prompt": "Prompt on each rename.", "typescript.updateImportsOnFileMove.enabled.always": "Always update paths automatically.", diff --git a/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts b/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts index bc0ac10388b..caeb91d1dc3 100644 --- a/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts +++ b/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as path from 'path'; import * as vscode from 'vscode'; import type * as Proto from '../protocol'; import { ITypeScriptServiceClient } from '../typescriptService'; @@ -189,6 +190,8 @@ export default class FileConfigurationManager extends Disposable { includeCompletionsWithSnippetText: config.get('suggest.includeCompletionsWithSnippetText', true), includeCompletionsWithClassMemberSnippets: config.get('suggest.classMemberSnippets.enabled', true), includeCompletionsWithObjectLiteralMethodSnippets: config.get('suggest.objectLiteralMethodSnippets.enabled', true), + // @ts-expect-error until TS 4.8 + autoImportFileExcludePatterns: this.getAutoImportFileExcludePatternsPreference(preferencesConfig, vscode.workspace.getWorkspaceFolder(document.uri)?.uri), useLabelDetailsInCompletionEntries: true, allowIncompleteCompletions: true, displayPartsForJSDoc: true, @@ -205,6 +208,18 @@ export default class FileConfigurationManager extends Disposable { default: return this.client.apiVersion.gte(API.v333) ? 'auto' : undefined; } } + + private getAutoImportFileExcludePatternsPreference(config: vscode.WorkspaceConfiguration, workspaceFolder: vscode.Uri | undefined): string[] | undefined { + return workspaceFolder && config.get('autoImportFileExcludePatterns')?.map(p => { + // Normalization rules: https://github.com/microsoft/TypeScript/pull/49578 + const slashNormalized = p.replace(/\\/g, '/'); + const isRelative = /^\.\.?($|\/)/.test(slashNormalized); + return path.isAbsolute(p) ? p : + p.startsWith('*') ? '/' + slashNormalized : + isRelative ? vscode.Uri.joinPath(workspaceFolder, p).fsPath : + '/**/' + slashNormalized; + }); + } } export class InlayHintSettingNames { From 1988843571f4a90bd7d440cc32a592dc5e199c82 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 28 Jun 2022 22:02:25 -0400 Subject: [PATCH 044/150] set icon from configuring task on contributed task (#153607) --- src/vs/workbench/contrib/tasks/common/taskConfiguration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts index ed7b1403249..c5d4058bcb0 100644 --- a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts +++ b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts @@ -1635,7 +1635,7 @@ namespace CustomTask { { name: configuredProps.configurationProperties.name || contributedTask.configurationProperties.name, identifier: configuredProps.configurationProperties.identifier || contributedTask.configurationProperties.identifier, - icon: contributedTask.configurationProperties.icon + icon: configuredProps.configurationProperties.icon }, ); From 0e3304bca9e862eddc7e437e2bab8a4a4a4f33c9 Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Tue, 28 Jun 2022 20:37:23 -0700 Subject: [PATCH 045/150] Change "Show Loaded Scripts" to "Open Loaded Script..." (#153628) Change debug console command to "Open Loaded Script..." Fixes #153446 --- src/vs/workbench/contrib/debug/browser/debug.contribution.ts | 4 ++-- src/vs/workbench/contrib/debug/browser/debugCommands.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index 7d10f4b8263..11584519c47 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -20,7 +20,7 @@ import { } from 'vs/workbench/contrib/debug/common/debug'; import { DebugToolBar } from 'vs/workbench/contrib/debug/browser/debugToolBar'; import { DebugService } from 'vs/workbench/contrib/debug/browser/debugService'; -import { ADD_CONFIGURATION_ID, TOGGLE_INLINE_BREAKPOINT_ID, COPY_STACK_TRACE_ID, RESTART_SESSION_ID, TERMINATE_THREAD_ID, STEP_OVER_ID, STEP_INTO_ID, STEP_OUT_ID, PAUSE_ID, DISCONNECT_ID, STOP_ID, RESTART_FRAME_ID, CONTINUE_ID, FOCUS_REPL_ID, JUMP_TO_CURSOR_ID, RESTART_LABEL, STEP_INTO_LABEL, STEP_OVER_LABEL, STEP_OUT_LABEL, PAUSE_LABEL, DISCONNECT_LABEL, STOP_LABEL, CONTINUE_LABEL, DEBUG_START_LABEL, DEBUG_START_COMMAND_ID, DEBUG_RUN_LABEL, DEBUG_RUN_COMMAND_ID, EDIT_EXPRESSION_COMMAND_ID, REMOVE_EXPRESSION_COMMAND_ID, SELECT_AND_START_ID, SELECT_AND_START_LABEL, SET_EXPRESSION_COMMAND_ID, DISCONNECT_AND_SUSPEND_ID, DISCONNECT_AND_SUSPEND_LABEL, NEXT_DEBUG_CONSOLE_ID, NEXT_DEBUG_CONSOLE_LABEL, PREV_DEBUG_CONSOLE_ID, PREV_DEBUG_CONSOLE_LABEL, SHOW_LOADED_SCRIPTS_LABEL, SHOW_LOADED_SCRIPTS_ID, DEBUG_QUICK_ACCESS_PREFIX, DEBUG_CONSOLE_QUICK_ACCESS_PREFIX, SELECT_DEBUG_CONSOLE_ID, SELECT_DEBUG_CONSOLE_LABEL, STEP_INTO_TARGET_LABEL, STEP_INTO_TARGET_ID } from 'vs/workbench/contrib/debug/browser/debugCommands'; +import { ADD_CONFIGURATION_ID, TOGGLE_INLINE_BREAKPOINT_ID, COPY_STACK_TRACE_ID, RESTART_SESSION_ID, TERMINATE_THREAD_ID, STEP_OVER_ID, STEP_INTO_ID, STEP_OUT_ID, PAUSE_ID, DISCONNECT_ID, STOP_ID, RESTART_FRAME_ID, CONTINUE_ID, FOCUS_REPL_ID, JUMP_TO_CURSOR_ID, RESTART_LABEL, STEP_INTO_LABEL, STEP_OVER_LABEL, STEP_OUT_LABEL, PAUSE_LABEL, DISCONNECT_LABEL, STOP_LABEL, CONTINUE_LABEL, DEBUG_START_LABEL, DEBUG_START_COMMAND_ID, DEBUG_RUN_LABEL, DEBUG_RUN_COMMAND_ID, EDIT_EXPRESSION_COMMAND_ID, REMOVE_EXPRESSION_COMMAND_ID, SELECT_AND_START_ID, SELECT_AND_START_LABEL, SET_EXPRESSION_COMMAND_ID, DISCONNECT_AND_SUSPEND_ID, DISCONNECT_AND_SUSPEND_LABEL, NEXT_DEBUG_CONSOLE_ID, NEXT_DEBUG_CONSOLE_LABEL, PREV_DEBUG_CONSOLE_ID, PREV_DEBUG_CONSOLE_LABEL, OPEN_LOADED_SCRIPTS_LABEL, SHOW_LOADED_SCRIPTS_ID, DEBUG_QUICK_ACCESS_PREFIX, DEBUG_CONSOLE_QUICK_ACCESS_PREFIX, SELECT_DEBUG_CONSOLE_ID, SELECT_DEBUG_CONSOLE_LABEL, STEP_INTO_TARGET_LABEL, STEP_INTO_TARGET_ID } from 'vs/workbench/contrib/debug/browser/debugCommands'; import { StatusBarColorProvider } from 'vs/workbench/contrib/debug/browser/statusbarColorProvider'; import { IViewsRegistry, Extensions as ViewExtensions, IViewContainersRegistry, ViewContainerLocation, ViewContainer } from 'vs/workbench/common/views'; import { isMacintosh, isWeb } from 'vs/base/common/platform'; @@ -133,7 +133,7 @@ registerDebugCommandPaletteItem(DEBUG_RUN_COMMAND_ID, DEBUG_RUN_LABEL, ContextKe registerDebugCommandPaletteItem(SELECT_AND_START_ID, SELECT_AND_START_LABEL, ContextKeyExpr.and(CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_STATE.notEqualsTo(getStateLabel(State.Initializing)))); registerDebugCommandPaletteItem(NEXT_DEBUG_CONSOLE_ID, NEXT_DEBUG_CONSOLE_LABEL); registerDebugCommandPaletteItem(PREV_DEBUG_CONSOLE_ID, PREV_DEBUG_CONSOLE_LABEL); -registerDebugCommandPaletteItem(SHOW_LOADED_SCRIPTS_ID, SHOW_LOADED_SCRIPTS_LABEL, CONTEXT_IN_DEBUG_MODE); +registerDebugCommandPaletteItem(SHOW_LOADED_SCRIPTS_ID, OPEN_LOADED_SCRIPTS_LABEL, CONTEXT_IN_DEBUG_MODE); registerDebugCommandPaletteItem(SELECT_DEBUG_CONSOLE_ID, SELECT_DEBUG_CONSOLE_LABEL); diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index dbd6b0e23fc..49eb8197d7a 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -82,7 +82,7 @@ export const DEBUG_START_LABEL = nls.localize('startDebug', "Start Debugging"); export const DEBUG_RUN_LABEL = nls.localize('startWithoutDebugging', "Start Without Debugging"); export const NEXT_DEBUG_CONSOLE_LABEL = nls.localize('nextDebugConsole', "Focus Next Debug Console"); export const PREV_DEBUG_CONSOLE_LABEL = nls.localize('prevDebugConsole', "Focus Previous Debug Console"); -export const SHOW_LOADED_SCRIPTS_LABEL = nls.localize('showLoadedScripts', "Show Loaded Scripts"); +export const OPEN_LOADED_SCRIPTS_LABEL = nls.localize('openLoadedScript', "Open Loaded Script..."); export const SELECT_DEBUG_CONSOLE_LABEL = nls.localize('selectDebugConsole', "Select Debug Console"); From ff744e89a63dcc65809d7686e1dc92946e227cc1 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Wed, 29 Jun 2022 09:14:56 +0200 Subject: [PATCH 046/150] Deprecate `editor.deltaDecorations` (#153529) * Fixes #148423: Deprecate `editor.deltaDecorations` * Fix compilation --- src/vs/editor/browser/editorBrowser.ts | 2 +- .../editor/browser/widget/codeEditorWidget.ts | 9 ++- .../editor/browser/widget/diffEditorWidget.ts | 8 +- .../colorPicker/browser/colorDetector.ts | 11 ++- .../contrib/find/browser/findDecorations.ts | 2 +- .../folding/browser/foldingDecorations.ts | 10 +-- .../contrib/folding/browser/foldingModel.ts | 8 +- .../folding/test/browser/foldingModel.test.ts | 12 +-- .../browser/inlayHintsController.ts | 2 +- .../browser/inlineCompletionsModel.ts | 23 +++--- src/vs/editor/contrib/links/browser/links.ts | 16 ++-- .../contrib/snippet/browser/snippetSession.ts | 2 +- .../test/browser/controller/cursor.test.ts | 22 +++--- .../browser/widget/codeEditorWidget.test.ts | 4 +- src/vs/monaco.d.ts | 2 +- .../browser/commentThreadRangeDecorator.ts | 12 ++- .../browser/breakpointEditorContribution.ts | 76 ++++++++++--------- 17 files changed, 126 insertions(+), 95 deletions(-) diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index 0fc6f298ccc..f91574dc4cb 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -852,7 +852,7 @@ export interface ICodeEditor extends editorCommon.IEditor { /** * All decorations added through this call will get the ownerId of this editor. - * @see {@link ITextModel.deltaDecorations} + * @deprecated */ deltaDecorations(oldDecorations: string[], newDecorations: IModelDeltaDecoration[]): string[]; diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 25e647366c8..1ba384d520f 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -267,7 +267,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE private _bannerDomNode: HTMLElement | null = null; - private _dropIntoEditorDecorationIds: string[] = []; + private _dropIntoEditorDecorations: EditorDecorationsCollection = this.createDecorationsCollection(); constructor( domElement: HTMLElement, @@ -1286,6 +1286,9 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE return this._modelData.model.getDecorationsInRange(range, this._id, filterValidationDecorations(this._configuration.options)); } + /** + * @deprecated + */ public deltaDecorations(oldDecorations: string[], newDecorations: IModelDeltaDecoration[]): string[] { if (!this._modelData) { return []; @@ -1844,12 +1847,12 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE options: CodeEditorWidget.dropIntoEditorDecorationOptions }]; - this._dropIntoEditorDecorationIds = this.deltaDecorations(this._dropIntoEditorDecorationIds, newDecorations); + this._dropIntoEditorDecorations.set(newDecorations); this.revealPosition(position, editorCommon.ScrollType.Immediate); } private removeDropIndicator(): void { - this._dropIntoEditorDecorationIds = this.deltaDecorations(this._dropIntoEditorDecorationIds, []); + this._dropIntoEditorDecorations.clear(); } } diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index f4a61dc0930..ee1a5a107b9 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -115,7 +115,9 @@ class VisualEditorState { this._zonesMap = {}; // (2) Model decorations - this._decorations = editor.deltaDecorations(this._decorations, []); + editor.changeDecorations((changeAccessor) => { + this._decorations = changeAccessor.deltaDecorations(this._decorations, []); + }); } public apply(editor: CodeEditorWidget, overviewRuler: editorBrowser.IOverviewRuler | null, newDecorations: IEditorDiffDecorationsWithZones, restoreScrollState: boolean): void { @@ -153,7 +155,9 @@ class VisualEditorState { scrollState?.restore(editor); // decorations - this._decorations = editor.deltaDecorations(this._decorations, newDecorations.decorations); + editor.changeDecorations((changeAccessor) => { + this._decorations = changeAccessor.deltaDecorations(this._decorations, newDecorations.decorations); + }); // overview ruler overviewRuler?.setZones(newDecorations.overviewZones); diff --git a/src/vs/editor/contrib/colorPicker/browser/colorDetector.ts b/src/vs/editor/contrib/colorPicker/browser/colorDetector.ts index 02ba389e69f..d4cd26914b1 100644 --- a/src/vs/editor/contrib/colorPicker/browser/colorDetector.ts +++ b/src/vs/editor/contrib/colorPicker/browser/colorDetector.ts @@ -172,10 +172,12 @@ export class ColorDetector extends Disposable implements IEditorContribution { options: ModelDecorationOptions.EMPTY })); - this._decorationsIds = this._editor.deltaDecorations(this._decorationsIds, decorations); + this._editor.changeDecorations((changeAccessor) => { + this._decorationsIds = changeAccessor.deltaDecorations(this._decorationsIds, decorations); - this._colorDatas = new Map(); - this._decorationsIds.forEach((id, i) => this._colorDatas.set(id, colorDatas[i])); + this._colorDatas = new Map(); + this._decorationsIds.forEach((id, i) => this._colorDatas.set(id, colorDatas[i])); + }); } private _colorDecorationClassRefs = this._register(new DisposableStore()); @@ -219,7 +221,8 @@ export class ColorDetector extends Disposable implements IEditorContribution { } private removeAllDecorations(): void { - this._decorationsIds = this._editor.deltaDecorations(this._decorationsIds, []); + this._editor.removeDecorations(this._decorationsIds); + this._decorationsIds = []; this._colorDecoratorIds.clear(); this._colorDecorationClassRefs.clear(); } diff --git a/src/vs/editor/contrib/find/browser/findDecorations.ts b/src/vs/editor/contrib/find/browser/findDecorations.ts index 16d9a207b03..4d1c39cd979 100644 --- a/src/vs/editor/contrib/find/browser/findDecorations.ts +++ b/src/vs/editor/contrib/find/browser/findDecorations.ts @@ -33,7 +33,7 @@ export class FindDecorations implements IDisposable { } public dispose(): void { - this._editor.deltaDecorations(this._allDecorations(), []); + this._editor.removeDecorations(this._allDecorations()); this._decorations = []; this._overviewRulerApproximateDecorations = []; diff --git a/src/vs/editor/contrib/folding/browser/foldingDecorations.ts b/src/vs/editor/contrib/folding/browser/foldingDecorations.ts index a171ad7059d..bdb63ab19e9 100644 --- a/src/vs/editor/contrib/folding/browser/foldingDecorations.ts +++ b/src/vs/editor/contrib/folding/browser/foldingDecorations.ts @@ -5,7 +5,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { IModelDecorationsChangeAccessor, IModelDeltaDecoration, TrackedRangeStickiness } from 'vs/editor/common/model'; +import { IModelDecorationsChangeAccessor, TrackedRangeStickiness } from 'vs/editor/common/model'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { IDecorationProvider } from 'vs/editor/contrib/folding/browser/foldingModel'; import { localize } from 'vs/nls'; @@ -72,11 +72,11 @@ export class FoldingDecorationProvider implements IDecorationProvider { } } - deltaDecorations(oldDecorations: string[], newDecorations: IModelDeltaDecoration[]): string[] { - return this.editor.deltaDecorations(oldDecorations, newDecorations); - } - changeDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => T): T { return this.editor.changeDecorations(callback); } + + removeDecorations(decorationIds: string[]): void { + this.editor.removeDecorations(decorationIds); + } } diff --git a/src/vs/editor/contrib/folding/browser/foldingModel.ts b/src/vs/editor/contrib/folding/browser/foldingModel.ts index c4c9f791b7c..ba52acfdea8 100644 --- a/src/vs/editor/contrib/folding/browser/foldingModel.ts +++ b/src/vs/editor/contrib/folding/browser/foldingModel.ts @@ -9,8 +9,8 @@ import { FoldingRegion, FoldingRegions, ILineRange } from './foldingRanges'; export interface IDecorationProvider { getDecorationOption(isCollapsed: boolean, isHidden: boolean): IModelDecorationOptions; - deltaDecorations(oldDecorations: string[], newDecorations: IModelDeltaDecoration[]): string[]; changeDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => T): T | null; + removeDecorations(decorationIds: string[]): void; } export interface FoldingModelChangeEvent { @@ -162,7 +162,9 @@ export class FoldingModel { k++; } - this._editorDecorationIds = this._decorationProvider.deltaDecorations(this._editorDecorationIds, newEditorDecorations); + this._decorationProvider.changeDecorations((changeAccessor) => { + this._editorDecorationIds = changeAccessor.deltaDecorations(this._editorDecorationIds, newEditorDecorations); + }); this._regions = newRegions; this._isInitialized = true; this._updateEventEmitter.fire({ model: this }); @@ -207,7 +209,7 @@ export class FoldingModel { } public dispose() { - this._decorationProvider.deltaDecorations(this._editorDecorationIds, []); + this._decorationProvider.removeDecorations(this._editorDecorationIds); } getAllRegionsAtLine(lineNumber: number, filter?: (r: FoldingRegion, level: number) => boolean): FoldingRegion[] { diff --git a/src/vs/editor/contrib/folding/test/browser/foldingModel.test.ts b/src/vs/editor/contrib/folding/test/browser/foldingModel.test.ts index f57e265bf09..9f2e3a0344c 100644 --- a/src/vs/editor/contrib/folding/test/browser/foldingModel.test.ts +++ b/src/vs/editor/contrib/folding/test/browser/foldingModel.test.ts @@ -7,7 +7,7 @@ import { escapeRegExpCharacters } from 'vs/base/common/strings'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel, TrackedRangeStickiness } from 'vs/editor/common/model'; +import { IModelDecorationsChangeAccessor, ITextModel, TrackedRangeStickiness } from 'vs/editor/common/model'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { FoldingModel, getNextFoldLine, getParentFoldLine, getPreviousFoldLine, setCollapseStateAtLevel, setCollapseStateForMatchingLines, setCollapseStateForRest, setCollapseStateLevelsDown, setCollapseStateLevelsUp, setCollapseStateUp } from 'vs/editor/contrib/folding/browser/foldingModel'; import { FoldingRegion } from 'vs/editor/contrib/folding/browser/foldingRanges'; @@ -59,14 +59,16 @@ export class TestDecorationProvider { return TestDecorationProvider.expandedDecoration; } - deltaDecorations(oldDecorations: string[], newDecorations: IModelDeltaDecoration[]): string[] { - return this.model.deltaDecorations(oldDecorations, newDecorations); - } - changeDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => T): (T | null) { return this.model.changeDecorations(callback); } + removeDecorations(decorationIds: string[]): void { + this.model.changeDecorations((changeAccessor) => { + changeAccessor.deltaDecorations(decorationIds, []); + }); + } + getDecorations(): ExpectedDecoration[] { const decorations = this.model.getAllDecorations(); const res: ExpectedDecoration[] = []; diff --git a/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts b/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts index 30c8a909369..4c09b306182 100644 --- a/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts +++ b/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts @@ -597,7 +597,7 @@ export class InlayHintsController implements IEditorContribution { } private _removeAllDecorations(): void { - this._editor.deltaDecorations(Array.from(this._decorationsMetadata.keys()), []); + this._editor.removeDecorations(Array.from(this._decorationsMetadata.keys())); for (const obj of this._decorationsMetadata.values()) { obj.classNameRef.dispose(); } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index 3c1fbb69804..b60a6dec142 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -577,18 +577,21 @@ export class SynchronizedInlineCompletionsCache extends Disposable { ) { super(); - const decorationIds = editor.deltaDecorations( - [], - completionsSource.items.map(i => ({ - range: i.range, - options: { - description: 'inline-completion-tracking-range' - }, - })) - ); + const decorationIds = editor.changeDecorations((changeAccessor) => { + return changeAccessor.deltaDecorations( + [], + completionsSource.items.map(i => ({ + range: i.range, + options: { + description: 'inline-completion-tracking-range' + }, + })) + ); + }); + this._register(toDisposable(() => { this.isDisposing = true; - editor.deltaDecorations(decorationIds, []); + editor.removeDecorations(decorationIds); })); this.completions = completionsSource.items.map((c, idx) => new CachedInlineCompletion(c, decorationIds[idx])); diff --git a/src/vs/editor/contrib/links/browser/links.ts b/src/vs/editor/contrib/links/browser/links.ts index 44a5e2bd74f..adcb05a6a1f 100644 --- a/src/vs/editor/contrib/links/browser/links.ts +++ b/src/vs/editor/contrib/links/browser/links.ts @@ -163,14 +163,16 @@ export class LinkDetector extends Disposable implements IEditorContribution { } } - const decorations = this.editor.deltaDecorations(oldDecorations, newDecorations); + this.editor.changeDecorations((changeAccessor) => { + const decorations = changeAccessor.deltaDecorations(oldDecorations, newDecorations); - this.currentOccurrences = {}; - this.activeLinkDecorationId = null; - for (let i = 0, len = decorations.length; i < len; i++) { - const occurence = new LinkOccurrence(links[i], decorations[i]); - this.currentOccurrences[occurence.decorationId] = occurence; - } + this.currentOccurrences = {}; + this.activeLinkDecorationId = null; + for (let i = 0, len = decorations.length; i < len; i++) { + const occurence = new LinkOccurrence(links[i], decorations[i]); + this.currentOccurrences[occurence.decorationId] = occurence; + } + }); } private _onEditorMouseMove(mouseEvent: ClickLinkMouseEvent, withKey: ClickLinkKeyboardEvent | null): void { diff --git a/src/vs/editor/contrib/snippet/browser/snippetSession.ts b/src/vs/editor/contrib/snippet/browser/snippetSession.ts index 3ad72329e91..00e8268b0b6 100644 --- a/src/vs/editor/contrib/snippet/browser/snippetSession.ts +++ b/src/vs/editor/contrib/snippet/browser/snippetSession.ts @@ -55,7 +55,7 @@ export class OneSnippet { dispose(): void { if (this._placeholderDecorations) { - this._editor.deltaDecorations([...this._placeholderDecorations.values()], []); + this._editor.removeDecorations([...this._placeholderDecorations.values()]); } this._placeholderGroups.length = 0; } diff --git a/src/vs/editor/test/browser/controller/cursor.test.ts b/src/vs/editor/test/browser/controller/cursor.test.ts index 41eba1043ba..3615f378627 100644 --- a/src/vs/editor/test/browser/controller/cursor.test.ts +++ b/src/vs/editor/test/browser/controller/cursor.test.ts @@ -502,18 +502,20 @@ suite('Editor Controller - Cursor', () => { ); withTestCodeEditor(model, { wrappingIndent: 'indent', wordWrap: 'wordWrapColumn', wordWrapColumn: 20 }, (editor, viewModel) => { - editor.deltaDecorations([], [ - { - range: new Range(1, 22, 1, 22), - options: { - showIfCollapsed: true, - description: 'test', - after: { - content: 'some very very very very very very very very long text', + editor.changeDecorations((changeAccessor) => { + changeAccessor.deltaDecorations([], [ + { + range: new Range(1, 22, 1, 22), + options: { + showIfCollapsed: true, + description: 'test', + after: { + content: 'some very very very very very very very very long text', + } } } - } - ]); + ]); + }); viewModel.setSelections('test', [new Selection(1, 1, 1, 1)]); const cursorPositions: any[] = []; diff --git a/src/vs/editor/test/browser/widget/codeEditorWidget.test.ts b/src/vs/editor/test/browser/widget/codeEditorWidget.test.ts index 878ac72db23..7fa0217572a 100644 --- a/src/vs/editor/test/browser/widget/codeEditorWidget.test.ts +++ b/src/vs/editor/test/browser/widget/codeEditorWidget.test.ts @@ -192,7 +192,9 @@ suite('CodeEditorWidget', () => { const calls: string[] = []; disposables.add(editor.onDidChangeModelContent((e) => { calls.push(`listener1 - contentchange(${e.changes.reduce((aggr, c) => [...aggr, c.text, c.rangeOffset, c.rangeLength], []).join(', ')})`); - editor.deltaDecorations([], [{ range: new Range(1, 1, 1, 1), options: { description: 'test' } }]); + editor.changeDecorations((changeAccessor) => { + changeAccessor.deltaDecorations([], [{ range: new Range(1, 1, 1, 1), options: { description: 'test' } }]); + }); })); disposables.add(editor.onDidChangeCursorSelection((e) => { calls.push(`listener1 - cursorchange(${e.selection.positionLineNumber}, ${e.selection.positionColumn})`); diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 03400969c32..713083bfe0c 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -5313,7 +5313,7 @@ declare namespace monaco.editor { getDecorationsInRange(range: Range): IModelDecoration[] | null; /** * All decorations added through this call will get the ownerId of this editor. - * @see {@link ITextModel.deltaDecorations} + * @deprecated */ deltaDecorations(oldDecorations: string[], newDecorations: IModelDeltaDecoration[]): string[]; /** diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadRangeDecorator.ts b/src/vs/workbench/contrib/comments/browser/commentThreadRangeDecorator.ts index 973762aa995..25b62cec998 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadRangeDecorator.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadRangeDecorator.ts @@ -84,8 +84,10 @@ export class CommentThreadRangeDecorator extends Disposable { } } } - this.activeDecorationIds = this.editor.deltaDecorations(this.activeDecorationIds, newDecoration); - newDecoration.forEach((decoration, index) => decoration.id = this.decorationIds[index]); + this.editor.changeDecorations((changeAccessor) => { + this.activeDecorationIds = changeAccessor.deltaDecorations(this.activeDecorationIds, newDecoration); + newDecoration.forEach((decoration, index) => decoration.id = this.decorationIds[index]); + }); } public update(editor: ICodeEditor, commentInfos: ICommentInfo[]) { @@ -122,8 +124,10 @@ export class CommentThreadRangeDecorator extends Disposable { }); } - this.decorationIds = editor.deltaDecorations(this.decorationIds, commentThreadRangeDecorations); - commentThreadRangeDecorations.forEach((decoration, index) => decoration.id = this.decorationIds[index]); + editor.changeDecorations((changeAccessor) => { + this.decorationIds = changeAccessor.deltaDecorations(this.decorationIds, commentThreadRangeDecorations); + commentThreadRangeDecorations.forEach((decoration, index) => decoration.id = this.decorationIds[index]); + }); } override dispose() { diff --git a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts index 7ed46f240ef..8fdc2c1f303 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts @@ -507,26 +507,28 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi this.ignoreDecorationsChangedEvent = true; // Set breakpoint decorations - const decorationIds = activeCodeEditor.deltaDecorations(this.breakpointDecorations.map(bpd => bpd.decorationId), desiredBreakpointDecorations); - this.breakpointDecorations.forEach(bpd => { - if (bpd.inlineWidget) { - bpd.inlineWidget.dispose(); - } - }); - this.breakpointDecorations = decorationIds.map((decorationId, index) => { - let inlineWidget: InlineBreakpointWidget | undefined = undefined; - const breakpoint = breakpoints[index]; - if (desiredBreakpointDecorations[index].options.before) { - const contextMenuActions = () => this.getContextMenuActions([breakpoint], activeCodeEditor.getModel().uri, breakpoint.lineNumber, breakpoint.column); - inlineWidget = new InlineBreakpointWidget(activeCodeEditor, decorationId, desiredBreakpointDecorations[index].options.glyphMarginClassName, breakpoint, this.debugService, this.contextMenuService, contextMenuActions); - } + activeCodeEditor.changeDecorations((changeAccessor) => { + const decorationIds = changeAccessor.deltaDecorations(this.breakpointDecorations.map(bpd => bpd.decorationId), desiredBreakpointDecorations); + this.breakpointDecorations.forEach(bpd => { + if (bpd.inlineWidget) { + bpd.inlineWidget.dispose(); + } + }); + this.breakpointDecorations = decorationIds.map((decorationId, index) => { + let inlineWidget: InlineBreakpointWidget | undefined = undefined; + const breakpoint = breakpoints[index]; + if (desiredBreakpointDecorations[index].options.before) { + const contextMenuActions = () => this.getContextMenuActions([breakpoint], activeCodeEditor.getModel().uri, breakpoint.lineNumber, breakpoint.column); + inlineWidget = new InlineBreakpointWidget(activeCodeEditor, decorationId, desiredBreakpointDecorations[index].options.glyphMarginClassName, breakpoint, this.debugService, this.contextMenuService, contextMenuActions); + } - return { - decorationId, - breakpoint, - range: desiredBreakpointDecorations[index].range, - inlineWidget - }; + return { + decorationId, + breakpoint, + range: desiredBreakpointDecorations[index].range, + inlineWidget + }; + }); }); } finally { this.ignoreDecorationsChangedEvent = false; @@ -535,23 +537,25 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi // Set breakpoint candidate decorations const session = this.debugService.getViewModel().focusedSession; const desiredCandidateDecorations = debugSettings.showInlineBreakpointCandidates && session ? await createCandidateDecorations(this.editor.getModel(), this.breakpointDecorations, session) : []; - const candidateDecorationIds = this.editor.deltaDecorations(this.candidateDecorations.map(c => c.decorationId), desiredCandidateDecorations); - this.candidateDecorations.forEach(candidate => { - candidate.inlineWidget.dispose(); - }); - this.candidateDecorations = candidateDecorationIds.map((decorationId, index) => { - const candidate = desiredCandidateDecorations[index]; - // Candidate decoration has a breakpoint attached when a breakpoint is already at that location and we did not yet set a decoration there - // In practice this happens for the first breakpoint that was set on a line - // We could have also rendered this first decoration as part of desiredBreakpointDecorations however at that moment we have no location information - const icon = candidate.breakpoint ? getBreakpointMessageAndIcon(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), candidate.breakpoint, this.labelService).icon : icons.breakpoint.disabled; - const contextMenuActions = () => this.getContextMenuActions(candidate.breakpoint ? [candidate.breakpoint] : [], activeCodeEditor.getModel().uri, candidate.range.startLineNumber, candidate.range.startColumn); - const inlineWidget = new InlineBreakpointWidget(activeCodeEditor, decorationId, ThemeIcon.asClassName(icon), candidate.breakpoint, this.debugService, this.contextMenuService, contextMenuActions); + this.editor.changeDecorations((changeAccessor) => { + const candidateDecorationIds = changeAccessor.deltaDecorations(this.candidateDecorations.map(c => c.decorationId), desiredCandidateDecorations); + this.candidateDecorations.forEach(candidate => { + candidate.inlineWidget.dispose(); + }); + this.candidateDecorations = candidateDecorationIds.map((decorationId, index) => { + const candidate = desiredCandidateDecorations[index]; + // Candidate decoration has a breakpoint attached when a breakpoint is already at that location and we did not yet set a decoration there + // In practice this happens for the first breakpoint that was set on a line + // We could have also rendered this first decoration as part of desiredBreakpointDecorations however at that moment we have no location information + const icon = candidate.breakpoint ? getBreakpointMessageAndIcon(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), candidate.breakpoint, this.labelService).icon : icons.breakpoint.disabled; + const contextMenuActions = () => this.getContextMenuActions(candidate.breakpoint ? [candidate.breakpoint] : [], activeCodeEditor.getModel().uri, candidate.range.startLineNumber, candidate.range.startColumn); + const inlineWidget = new InlineBreakpointWidget(activeCodeEditor, decorationId, ThemeIcon.asClassName(icon), candidate.breakpoint, this.debugService, this.contextMenuService, contextMenuActions); - return { - decorationId, - inlineWidget - }; + return { + decorationId, + inlineWidget + }; + }); }); for (const d of this.breakpointDecorations) { @@ -631,7 +635,7 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi if (this.breakpointWidget) { this.breakpointWidget.dispose(); } - this.editor.deltaDecorations(this.breakpointDecorations.map(bpd => bpd.decorationId), []); + this.editor.removeDecorations(this.breakpointDecorations.map(bpd => bpd.decorationId)); dispose(this.toDispose); } } From 56cade24a397b533a4d226c23dbf2306468db00f Mon Sep 17 00:00:00 2001 From: Harald Kirschner Date: Wed, 29 Jun 2022 10:14:39 +0200 Subject: [PATCH 047/150] Make the guided git.missing welcome view default (#149668) --- extensions/git/package.json | 58 ++++++++++++--------------------- extensions/git/package.nls.json | 16 +++------ 2 files changed, 24 insertions(+), 50 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 7e4cc563f34..0bec01c30a5 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -2438,19 +2438,6 @@ "default": 10000, "description": "%config.statusLimit%" }, - "git.experimental.installGuide": { - "type": "string", - "enum": [ - "default", - "download" - ], - "tags": [ - "experimental" - ], - "scope": "machine", - "description": "%config.experimental.installGuide%", - "default": "default" - }, "git.repositoryScanIgnoredFolders": { "type": "array", "items": { @@ -2627,56 +2614,51 @@ "contents": "%view.workbench.scm.disabled%", "when": "!config.git.enabled" }, - { - "view": "scm", - "contents": "%view.workbench.scm.missing.guide%", - "when": "config.git.enabled && git.missing && config.git.experimental.installGuide == download" - }, - { - "view": "scm", - "contents": "%view.workbench.scm.missing.guide.mac%", - "when": "config.git.enabled && git.missing && config.git.experimental.installGuide == download && isMac" - }, - { - "view": "scm", - "contents": "%view.workbench.scm.missing.guide.windows%", - "when": "config.git.enabled && git.missing && config.git.experimental.installGuide == download && isWindows" - }, - { - "view": "scm", - "contents": "%view.workbench.scm.missing.guide.linux%", - "when": "config.git.enabled && git.missing && config.git.experimental.installGuide == download && isLinux" - }, { "view": "scm", "contents": "%view.workbench.scm.missing%", - "when": "config.git.enabled && git.missing && config.git.experimental.installGuide == default" + "when": "config.git.enabled && git.missing" + }, + { + "view": "scm", + "contents": "%view.workbench.scm.missing.mac%", + "when": "config.git.enabled && git.missing && isMac" + }, + { + "view": "scm", + "contents": "%view.workbench.scm.missing.windows%", + "when": "config.git.enabled && git.missing && isWindows" + }, + { + "view": "scm", + "contents": "%view.workbench.scm.missing.linux%", + "when": "config.git.enabled && git.missing && isLinux" }, { "view": "scm", "contents": "%view.workbench.scm.empty%", - "when": "config.git.enabled && workbenchState == empty", + "when": "config.git.enabled && !git.missing && workbenchState == empty", "enablement": "git.state == initialized", "group": "2_open@1" }, { "view": "scm", "contents": "%view.workbench.scm.folder%", - "when": "config.git.enabled && workbenchState == folder", + "when": "config.git.enabled && !git.missing && workbenchState == folder", "enablement": "git.state == initialized", "group": "5_scm@1" }, { "view": "scm", "contents": "%view.workbench.scm.workspace%", - "when": "config.git.enabled && workbenchState == workspace && workspaceFolderCount != 0", + "when": "config.git.enabled && !git.missing && workbenchState == workspace && workspaceFolderCount != 0", "enablement": "git.state == initialized", "group": "5_scm@1" }, { "view": "scm", "contents": "%view.workbench.scm.emptyWorkspace%", - "when": "config.git.enabled && workbenchState == workspace && workspaceFolderCount == 0", + "when": "config.git.enabled && !git.missing && workbenchState == workspace && workspaceFolderCount == 0", "enablement": "git.state == initialized", "group": "2_open@1" }, diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index e632bb47202..2d6c88e59e1 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -264,15 +264,7 @@ "colors.ignored": "Color for ignored resources.", "colors.conflict": "Color for resources with conflicts.", "colors.submodule": "Color for submodule resources.", - "view.workbench.scm.missing": { - "message": "A valid git installation was not detected, more details can be found in the [git output](command:git.showOutput).\nPlease [install git](https://git-scm.com/), or learn more about how to use git and source control in VS Code in [our docs](https://aka.ms/vscode-scm).\nIf you're using a different version control system, you can [search the Marketplace](command:workbench.extensions.search?%22%40category%3A%5C%22scm%20providers%5C%22%22) for additional extensions.", - "comment": [ - "{Locked='](command:git.showOutput'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", - "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" - ] - }, - "view.workbench.scm.missing.guide.windows": { + "view.workbench.scm.missing.windows": { "message": "[Download Git for Windows](https://git-scm.com/download/win)\nAfter installing, please [reload](command:workbench.action.reloadWindow) (or [troubleshoot](command:git.showOutput)). Additional source control providers can be installed [from the Marketplace](command:workbench.extensions.search?%22%40category%3A%5C%22scm%20providers%5C%22%22).", "comment": [ "{Locked='](command:workbench.action.reloadWindow'}", @@ -280,7 +272,7 @@ "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, - "view.workbench.scm.missing.guide.mac": { + "view.workbench.scm.missing.mac": { "message": "[Download Git for macOS](https://git-scm.com/download/mac)\nAfter installing, please [reload](command:workbench.action.reloadWindow) (or [troubleshoot](command:git.showOutput)). Additional source control providers can be installed [from the Marketplace](command:workbench.extensions.search?%22%40category%3A%5C%22scm%20providers%5C%22%22).", "comment": [ "{Locked='](command:workbench.action.reloadWindow'}", @@ -288,7 +280,7 @@ "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, - "view.workbench.scm.missing.guide.linux": { + "view.workbench.scm.missing.linux": { "message": "Source control depends on Git being installed.\n[Download Git for Linux](https://git-scm.com/download/linux)\nAfter installing, please [reload](command:workbench.action.reloadWindow) (or [troubleshoot](command:git.showOutput)). Additional source control providers can be installed [from the Marketplace](command:workbench.extensions.search?%22%40category%3A%5C%22scm%20providers%5C%22%22).", "comment": [ "{Locked='](command:workbench.action.reloadWindow'}", @@ -296,7 +288,7 @@ "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, - "view.workbench.scm.missing.guide": "Install Git, a popular source control system, to track code changes and collaborate with others. Learn more in our [Git guides](https://aka.ms/vscode-scm).", + "view.workbench.scm.missing": "Install Git, a popular source control system, to track code changes and collaborate with others. Learn more in our [Git guides](https://aka.ms/vscode-scm).", "view.workbench.scm.disabled": { "message": "If you would like to use git features, please enable git in your [settings](command:workbench.action.openSettings?%5B%22git.enabled%22%5D).\nTo learn more about how to use git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", "comment": [ From 6670977b727d79d4dc5ac4757a23ace3707c3dca Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 29 Jun 2022 11:52:35 +0200 Subject: [PATCH 048/150] Fix #153434 (#153656) --- .../contrib/userDataProfile/common/userDataProfileActions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts b/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts index 222b1f2399f..e796f1f897e 100644 --- a/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts +++ b/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts @@ -147,7 +147,7 @@ registerAction2(class SwitchProfileAction extends Action2 { const userDataProfilesService = accessor.get(IUserDataProfilesService); const userDataProfileManagementService = accessor.get(IUserDataProfileManagementService); - const profiles = userDataProfilesService.profiles.filter(p => p.id !== userDataProfileService.currentProfile.id); + const profiles = userDataProfilesService.profiles; if (profiles.length) { const picks: Array = profiles.map(profile => ({ label: profile.name!, From cc434a9f38b250372d5941c9d211e05ab3a7b7fa Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 29 Jun 2022 11:54:59 +0200 Subject: [PATCH 049/150] Git - do not add a separate after the last command group (#153649) Fix #153501 --- src/vs/workbench/contrib/scm/browser/scmViewPane.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 350110a01f9..4d9461ae8aa 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -2629,11 +2629,13 @@ export class SCMActionButton implements IDisposable { if (button.secondaryCommands?.length) { const actions: IAction[] = []; - for (const commandGroup of button.secondaryCommands) { - for (const command of commandGroup) { + for (let index = 0; index < button.secondaryCommands.length; index++) { + for (const command of button.secondaryCommands[index]) { actions.push(new Action(command.id, command.title, undefined, true, async () => await executeButtonAction(command.id, ...(command.arguments || [])))); } - actions.push(new Separator()); + if (index !== button.secondaryCommands.length - 1) { + actions.push(new Separator()); + } } // ButtonWithDropdown From b10ca0ef2747f56a4d8656c0cdc33ff4a18de9bc Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 29 Jun 2022 12:31:42 +0200 Subject: [PATCH 050/150] Tweaks checkbox tooltip. --- .../mergeEditor/browser/view/editors/inputCodeEditorView.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts index 8e974f8c13e..c1d9ca82842 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts @@ -238,7 +238,7 @@ export class MergeConflictGutterItemView extends Disposable implements IGutterIt target.classList.add('merge-accept-gutter-marker'); - const checkBox = new Toggle({ isChecked: false, title: localize('acceptMerge', "Accept Merge"), icon: Codicon.check }); + const checkBox = new Toggle({ isChecked: false, title: localize('accept', "Accept"), icon: Codicon.check }); this._register( dom.addDisposableListener(checkBox.domNode, dom.EventType.MOUSE_DOWN, (e) => { if (e.button === 2) { From 79a14293aa9f99c123e820b6b3748b356015b9c6 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 29 Jun 2022 12:46:02 +0200 Subject: [PATCH 051/150] Fixes #153579 --- .../view/editors/inputCodeEditorView.ts | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts index c1d9ca82842..60f6e26ce0e 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts @@ -152,29 +152,32 @@ export class InputCodeEditorView extends CodeEditorView { return [ baseRange.input1Diffs.length > 0 ? action( - 'mergeEditor.takeInput1', - localize('mergeEditor.takeInput1', 'Take Input 1'), + 'mergeEditor.acceptInput1', + localize('mergeEditor.accept', 'Accept ') + model.input1Title, state.toggle(1), state.input1 ) : undefined, baseRange.input2Diffs.length > 0 ? action( - 'mergeEditor.takeInput2', - localize('mergeEditor.takeInput2', 'Take Input 2'), + 'mergeEditor.acceptInput2', + localize('mergeEditor.accept', 'Accept ') + model.input2Title, state.toggle(2), state.input2 ) : undefined, baseRange.isConflicting - ? action( - 'mergeEditor.takeBothSides', - localize( - 'mergeEditor.takeBothSides', - 'Take Both Sides' + ? setFields( + action( + 'mergeEditor.acceptBoth', + localize( + 'mergeEditor.acceptBoth', + 'Accept Both' + ), + state.withInput1(!both).withInput2(!both), + both ), - state.withInput1(!both).withInput2(!both), - both + { enabled: true } // TODO ) : undefined, baseRange.isConflicting From a424998dfd7cd3043c558f0f4b64994f601c1981 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 29 Jun 2022 12:48:22 +0200 Subject: [PATCH 052/150] Git - Action button precedence (#153662) --- extensions/git/src/actionButton.ts | 203 ++++++++++++++--------------- 1 file changed, 97 insertions(+), 106 deletions(-) diff --git a/extensions/git/src/actionButton.ts b/extensions/git/src/actionButton.ts index bfbb490d102..f62cb214879 100644 --- a/extensions/git/src/actionButton.ts +++ b/extensions/git/src/actionButton.ts @@ -14,7 +14,7 @@ const localize = nls.loadMessageBundle(); interface ActionButtonState { readonly HEAD: Branch | undefined; readonly isActionRunning: boolean; - readonly repositoryHasNoChanges: boolean; + readonly repositoryHasChanges: boolean; } export class ActionButtonCommand { @@ -33,7 +33,7 @@ export class ActionButtonCommand { private disposables: Disposable[] = []; constructor(readonly repository: Repository) { - this._state = { HEAD: undefined, isActionRunning: false, repositoryHasNoChanges: false }; + this._state = { HEAD: undefined, isActionRunning: false, repositoryHasChanges: false }; repository.onDidRunGitStatus(this.onDidRunGitStatus, this, this.disposables); repository.onDidChangeOperations(this.onDidChangeOperations, this, this.disposables); @@ -52,134 +52,125 @@ export class ActionButtonCommand { if (!this.state.HEAD || !this.state.HEAD.name) { return undefined; } let actionButton: SourceControlActionButton | undefined; - if (this.state.repositoryHasNoChanges) { - if (this.state.HEAD.upstream) { - // Sync Changes - actionButton = this.getSyncChangesActionButton(); - } else { - // Publish Branch - actionButton = this.getPublishBranchActionButton(); - } - } else { - // Commit Changes + + if (this.state.repositoryHasChanges) { + // Commit Changes (enabled) actionButton = this.getCommitActionButton(); } - return actionButton; + // Commit Changes (enabled) -> Publish Branch -> Sync Changes -> Commit Changes (disabled) + return actionButton ?? this.getPublishBranchActionButton() ?? this.getSyncChangesActionButton() ?? this.getCommitActionButton(); } private getCommitActionButton(): SourceControlActionButton | undefined { const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); const showActionButton = config.get<{ commit: boolean }>('showActionButton', { commit: true }); - if (showActionButton.commit) { - let title: string, tooltip: string; - const postCommitCommand = config.get('postCommitCommand'); + // The button is disabled + if (!showActionButton.commit) { return undefined; } - switch (postCommitCommand) { - case 'push': { - title = localize('scm button commit and push title', "$(arrow-up) Commit & Push"); - tooltip = this.state.isActionRunning ? - localize('scm button committing pushing tooltip', "Committing & Pushing Changes...") : - localize('scm button commit push tooltip', "Commit & Push Changes"); - break; - } - case 'sync': { - title = localize('scm button commit and sync title', "$(sync) Commit & Sync"); - tooltip = this.state.isActionRunning ? - localize('scm button committing synching tooltip', "Committing & Synching Changes...") : - localize('scm button commit sync tooltip', "Commit & Sync Changes"); - break; - } - default: { - title = localize('scm button commit title', "$(check) Commit"); - tooltip = this.state.isActionRunning ? - localize('scm button committing tooltip', "Committing Changes...") : - localize('scm button commit tooltip', "Commit Changes"); - break; - } + let title: string, tooltip: string; + const postCommitCommand = config.get('postCommitCommand'); + + switch (postCommitCommand) { + case 'push': { + title = localize('scm button commit and push title', "$(arrow-up) Commit & Push"); + tooltip = this.state.isActionRunning ? + localize('scm button committing pushing tooltip', "Committing & Pushing Changes...") : + localize('scm button commit push tooltip', "Commit & Push Changes"); + break; + } + case 'sync': { + title = localize('scm button commit and sync title', "$(sync) Commit & Sync"); + tooltip = this.state.isActionRunning ? + localize('scm button committing synching tooltip', "Committing & Synching Changes...") : + localize('scm button commit sync tooltip', "Commit & Sync Changes"); + break; + } + default: { + title = localize('scm button commit title', "$(check) Commit"); + tooltip = this.state.isActionRunning ? + localize('scm button committing tooltip', "Committing Changes...") : + localize('scm button commit tooltip', "Commit Changes"); + break; } - - return { - command: { - command: 'git.commit', - title: title, - tooltip: tooltip, - arguments: [this.repository.sourceControl], - }, - secondaryCommands: [ - [ - { - command: 'git.commit', - title: localize('scm secondary button commit', "Commit"), - arguments: [this.repository.sourceControl, ''], - }, - { - command: 'git.commit', - title: localize('scm secondary button commit and push', "Commit & Push"), - arguments: [this.repository.sourceControl, 'push'], - }, - { - command: 'git.commit', - title: localize('scm secondary button commit and sync', "Commit & Sync"), - arguments: [this.repository.sourceControl, 'sync'], - }, - ] - ], - enabled: !this.state.isActionRunning - }; } - return undefined; + return { + command: { + command: 'git.commit', + title: title, + tooltip: tooltip, + arguments: [this.repository.sourceControl], + }, + secondaryCommands: [ + [ + { + command: 'git.commit', + title: localize('scm secondary button commit', "Commit"), + arguments: [this.repository.sourceControl, ''], + }, + { + command: 'git.commit', + title: localize('scm secondary button commit and push', "Commit & Push"), + arguments: [this.repository.sourceControl, 'push'], + }, + { + command: 'git.commit', + title: localize('scm secondary button commit and sync', "Commit & Sync"), + arguments: [this.repository.sourceControl, 'sync'], + }, + ] + ], + enabled: this.state.repositoryHasChanges && !this.state.isActionRunning + }; } private getPublishBranchActionButton(): SourceControlActionButton | undefined { const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); const showActionButton = config.get<{ publish: boolean }>('showActionButton', { publish: true }); - if (showActionButton.publish) { - return { - command: { - command: 'git.publish', - title: localize('scm publish branch action button title', "{0} Publish Branch", '$(cloud-upload)'), - tooltip: this.state.isActionRunning ? - localize('scm button publish branch running', "Publishing Branch...") : - localize('scm button publish branch', "Publish Branch"), - arguments: [this.repository.sourceControl], - }, - enabled: !this.state.isActionRunning - }; - } + // Branch does have an upstream or the button is disabled + if (this.state.HEAD?.upstream || !showActionButton.publish) { return undefined; } - return undefined; + return { + command: { + command: 'git.publish', + title: localize('scm publish branch action button title', "{0} Publish Branch", '$(cloud-upload)'), + tooltip: this.state.isActionRunning ? + localize('scm button publish branch running', "Publishing Branch...") : + localize('scm button publish branch', "Publish Branch"), + arguments: [this.repository.sourceControl], + }, + enabled: !this.state.isActionRunning + }; } private getSyncChangesActionButton(): SourceControlActionButton | undefined { const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); const showActionButton = config.get<{ sync: boolean }>('showActionButton', { sync: true }); - if (this.state.HEAD?.ahead && showActionButton.sync) { - const rebaseWhenSync = config.get('rebaseWhenSync'); + // Branch does not have an upstream or the button is disabled + if (!this.state.HEAD?.upstream || !showActionButton.sync) { return undefined; } - const ahead = `${this.state.HEAD.ahead}$(arrow-up)`; - const behind = this.state.HEAD.behind ? `${this.state.HEAD.behind}$(arrow-down) ` : ''; - const icon = this.state.isActionRunning ? '$(sync~spin)' : '$(sync)'; + const ahead = this.state.HEAD.ahead ? ` ${this.state.HEAD.ahead}$(arrow-up)` : ''; + const behind = this.state.HEAD.behind ? ` ${this.state.HEAD.behind}$(arrow-down)` : ''; + const icon = this.state.isActionRunning ? '$(sync~spin)' : '$(sync)'; - return { - command: { - command: rebaseWhenSync ? 'git.syncRebase' : 'git.sync', - title: `${icon} ${behind} ${ahead}`, - tooltip: this.state.isActionRunning ? - localize('syncing changes', "Synchronizing Changes...") - : this.repository.syncTooltip, - arguments: [this.repository.sourceControl], - }, - description: localize('scm button sync description', "{0} Sync Changes {1}{2}", icon, behind, ahead), - enabled: !this.state.isActionRunning - }; - } + const rebaseWhenSync = config.get('rebaseWhenSync'); - return undefined; + return { + command: { + command: rebaseWhenSync ? 'git.syncRebase' : 'git.sync', + title: `${icon}${behind}${ahead}`, + tooltip: this.state.isActionRunning ? + localize('syncing changes', "Synchronizing Changes...") + : this.repository.syncTooltip, + arguments: [this.repository.sourceControl], + }, + description: localize('scm button sync description', "{0} Sync Changes{1}{2}", icon, behind, ahead), + enabled: !this.state.isActionRunning + }; } private onDidChangeOperations(): void { @@ -196,11 +187,11 @@ export class ActionButtonCommand { this.state = { ...this.state, HEAD: this.repository.HEAD, - repositoryHasNoChanges: - this.repository.indexGroup.resourceStates.length === 0 && - this.repository.mergeGroup.resourceStates.length === 0 && - this.repository.untrackedGroup.resourceStates.length === 0 && - this.repository.workingTreeGroup.resourceStates.length === 0 + repositoryHasChanges: + this.repository.indexGroup.resourceStates.length !== 0 || + this.repository.mergeGroup.resourceStates.length !== 0 || + this.repository.untrackedGroup.resourceStates.length !== 0 || + this.repository.workingTreeGroup.resourceStates.length !== 0 }; } From 1634eaec832e45a25b951b6205503b4972fd4d1c Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 29 Jun 2022 14:02:34 +0200 Subject: [PATCH 053/150] Share link shows with untitled document (#153671) Fixes #153544 --- extensions/github/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/github/package.json b/extensions/github/package.json index fd5b8a9dfca..ed50579d22c 100644 --- a/extensions/github/package.json +++ b/extensions/github/package.json @@ -59,7 +59,7 @@ "editor/context/share": [ { "command": "github.copyVscodeDevLink", - "when": "github.hasGitHubRepo" + "when": "github.hasGitHubRepo && resourceScheme != untitled" } ] }, From 39e59f970aa3917d7915813187f5c94657adb523 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 29 Jun 2022 14:06:35 +0200 Subject: [PATCH 054/150] check version while switching profiles (#153672) * Fix #153646 * revert change in abstractExtensionService --- .../common/profileAwareExtensionManagementService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/services/extensionManagement/common/profileAwareExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/profileAwareExtensionManagementService.ts index 291e62a8317..781b4557058 100644 --- a/src/vs/workbench/services/extensionManagement/common/profileAwareExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/profileAwareExtensionManagementService.ts @@ -64,7 +64,7 @@ export class NativeProfileAwareExtensionManagementService extends ExtensionManag const oldExtensions = await this.getInstalled(ExtensionType.User); this.extensionsProfileResource = extensionsProfileResource; const newExtensions = await this.getInstalled(ExtensionType.User); - const { added, removed } = delta(oldExtensions, newExtensions, (a, b) => compare(ExtensionIdentifier.toKey(a.identifier.id), ExtensionIdentifier.toKey(b.identifier.id))); + const { added, removed } = delta(oldExtensions, newExtensions, (a, b) => compare(`${ExtensionIdentifier.toKey(a.identifier.id)}@${a.manifest.version}`, `${ExtensionIdentifier.toKey(b.identifier.id)}@${b.manifest.version}`)); if (added.length || removed.length) { this._onDidChangeProfileExtensions.fire({ added, removed }); } From bd24f72d42d6ced671ee27803ec3bdbe5be7162e Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 29 Jun 2022 14:08:09 +0200 Subject: [PATCH 055/150] fix 153492 (#153667) * enable "accept merge" for merge editor only * close merge editor before staging file, only stage file when editor has been closed fixes https://github.com/microsoft/vscode/issues/153492 --- extensions/git/package.json | 3 ++- extensions/git/src/commands.ts | 10 ++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 0bec01c30a5..32c70fed3b7 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -586,7 +586,8 @@ { "command": "git.acceptMerge", "title": "%command.git.acceptMerge%", - "category": "Git" + "category": "Git", + "enablement": "isMergeEditor" } ], "keybindings": [ diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index e519e1de1c9..564206bfa58 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -1106,13 +1106,19 @@ export class CommandCenter { } await doc.save(); - await repository.add([uri]); // TODO@jrieken there isn't a `TabInputTextMerge` instance yet, till now the merge editor // uses the `TabInputText` for the out-resource and we use that to identify and CLOSE the tab const { activeTab } = window.tabGroups.activeTabGroup; + let didCloseTab = false; if (activeTab && activeTab?.input instanceof TabInputText && activeTab.input.uri.toString() === uri.toString()) { - await window.tabGroups.close(activeTab, true); + didCloseTab = await window.tabGroups.close(activeTab, true); + } + + // Only stage if the merge editor has been successfully closed. That means all conflicts have been + // handled or unhandled conflicts are OK by the user. + if (didCloseTab) { + await repository.add([uri]); } } From 4a7f37ff1dbff098e6f6d0c6e3c690d98f55c861 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 29 Jun 2022 14:15:58 +0200 Subject: [PATCH 056/150] Fix #153530 (#153674) --- .../browser/extensionsWorkbenchService.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index d04b4af2ef6..a533f0ad244 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -32,7 +32,7 @@ import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/ import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import * as resources from 'vs/base/common/resources'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { IStorageService, IStorageValueChangeEvent, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IFileService } from 'vs/platform/files/common/files'; import { IExtensionManifest, ExtensionType, IExtension as IPlatformExtension, TargetPlatform, ExtensionIdentifier, IExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { ILanguageService } from 'vs/editor/common/languages/language'; @@ -776,6 +776,8 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension this.updateContexts(); this.updateActivity(); })); + + this._register(this.storageService.onDidChangeValue(e => this.onDidChangeStorage(e))); } private _reportTelemetry() { const extensionIds = this.installed.filter(extension => @@ -1595,6 +1597,8 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension }).then(undefined, error => this.onError(error)); } + //#region Ignore Autoupdates when specific versions are installed + /* TODO: @sandy081 Extension version shall be moved to extensions.json file */ private _ignoredAutoUpdateExtensions: string[] | undefined; private get ignoredAutoUpdateExtensions(): string[] { @@ -1609,6 +1613,12 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension this.storageService.store('extensions.ignoredAutoUpdateExtension', JSON.stringify(this._ignoredAutoUpdateExtensions), StorageScope.PROFILE, StorageTarget.MACHINE); } + private onDidChangeStorage(e: IStorageValueChangeEvent): void { + if (e.scope === StorageScope.PROFILE && e.key === 'extensions.ignoredAutoUpdateExtension') { + this._ignoredAutoUpdateExtensions = undefined; + } + } + private ignoreAutoUpdate(extensionKey: ExtensionKey): void { if (!this.isAutoUpdateIgnored(extensionKey)) { this.ignoredAutoUpdateExtensions = [...this.ignoredAutoUpdateExtensions, extensionKey.toString()]; @@ -1623,4 +1633,6 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension this.ignoredAutoUpdateExtensions = this.ignoredAutoUpdateExtensions.filter(extensionId => this.local.some(local => !!local.local && new ExtensionKey(local.identifier, local.version).toString() === extensionId)); } + //#endregion + } From 82eb075ad3a8b2aa7f18d95314019197bb416143 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 29 Jun 2022 14:17:27 +0200 Subject: [PATCH 057/150] Removes NLS string concatenation. --- .../mergeEditor/browser/view/editors/inputCodeEditorView.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts index 60f6e26ce0e..2b27e226680 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts @@ -153,7 +153,7 @@ export class InputCodeEditorView extends CodeEditorView { baseRange.input1Diffs.length > 0 ? action( 'mergeEditor.acceptInput1', - localize('mergeEditor.accept', 'Accept ') + model.input1Title, + localize('mergeEditor.accept', 'Accept {0}', model.input1Title), state.toggle(1), state.input1 ) @@ -161,7 +161,7 @@ export class InputCodeEditorView extends CodeEditorView { baseRange.input2Diffs.length > 0 ? action( 'mergeEditor.acceptInput2', - localize('mergeEditor.accept', 'Accept ') + model.input2Title, + localize('mergeEditor.accept', 'Accept {0}', model.input2Title), state.toggle(2), state.input2 ) From d3810b767b94b1a67e345bba57ec9b86a80e3cc8 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 29 Jun 2022 14:18:07 +0200 Subject: [PATCH 058/150] Don't show "Accept both" when not both can be accepted. Don't show "Swap" when it does not have an effect. --- .../browser/model/mergeEditorModel.ts | 118 +-------------- .../browser/model/modifiedBaseRange.ts | 141 ++++++++++++++++++ .../view/editors/inputCodeEditorView.ts | 17 ++- 3 files changed, 156 insertions(+), 120 deletions(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts index a03d95ea043..d9f61d89baf 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts @@ -3,23 +3,18 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { compareBy, CompareResult, equals, numberComparator, tieBreakComparators } from 'vs/base/common/arrays'; +import { CompareResult, equals } from 'vs/base/common/arrays'; import { BugIndicatingError } from 'vs/base/common/errors'; -import { splitLines } from 'vs/base/common/strings'; -import { Constants } from 'vs/base/common/uint'; -import { Position } from 'vs/editor/common/core/position'; -import { Range } from 'vs/editor/common/core/range'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; import { EditorModel } from 'vs/workbench/common/editor/editorModel'; import { autorunHandleChanges, derivedObservable, IObservable, IReader, ITransaction, keepAlive, ObservableValue, transaction, waitForState } from 'vs/workbench/contrib/audioCues/browser/observable'; import { IDiffComputer } from 'vs/workbench/contrib/mergeEditor/browser/model/diffComputer'; -import { LineRangeEdit, RangeEdit } from 'vs/workbench/contrib/mergeEditor/browser/model/editing'; import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange'; import { DetailedLineRangeMapping, DocumentMapping, LineRangeMapping } from 'vs/workbench/contrib/mergeEditor/browser/model/mapping'; import { TextModelDiffChangeReason, TextModelDiffs, TextModelDiffState } from 'vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs'; -import { concatArrays, elementAtOrUndefined, leftJoin } from 'vs/workbench/contrib/mergeEditor/browser/utils'; +import { leftJoin } from 'vs/workbench/contrib/mergeEditor/browser/utils'; import { ModifiedBaseRange, ModifiedBaseRangeState } from './modifiedBaseRange'; export const enum MergeEditorModelState { @@ -269,7 +264,7 @@ export class MergeEditorModel extends EditorModel { this.resultTextModelDiffs.removeDiffs(conflictingDiffs, transaction); } - const { edit, effectiveState } = getEditForBase(baseRange, state); + const { edit, effectiveState } = baseRange.getEditForBase(state); existingState.set(effectiveState, transaction); @@ -312,7 +307,7 @@ export class MergeEditorModel extends EditorModel { ]; for (const s of states) { - const { edit } = getEditForBase(baseRange, s); + const { edit } = baseRange.getEditForBase(s); if (edit) { const resultRange = this.resultTextModelDiffs.getResultRange(baseRange.baseRange); const existingLines = resultRange.getLines(this.result); @@ -342,108 +337,3 @@ export class MergeEditorModel extends EditorModel { this.modelService.setMode(this.result, language); } } - -function getEditForBase(baseRange: ModifiedBaseRange, state: ModifiedBaseRangeState): { edit: LineRangeEdit | undefined; effectiveState: ModifiedBaseRangeState } { - const diffs = concatArrays( - state.input1 && baseRange.input1CombinedDiff ? [{ diff: baseRange.input1CombinedDiff, inputNumber: 1 as const }] : [], - state.input2 && baseRange.input2CombinedDiff ? [{ diff: baseRange.input2CombinedDiff, inputNumber: 2 as const }] : [], - ); - - if (state.input2First) { - diffs.reverse(); - } - - const firstDiff = elementAtOrUndefined(diffs, 0); - const secondDiff = elementAtOrUndefined(diffs, 1); - - if (!firstDiff) { - return { edit: undefined, effectiveState: ModifiedBaseRangeState.default }; - } - if (!secondDiff) { - return { edit: firstDiff.diff.getLineEdit(), effectiveState: ModifiedBaseRangeState.default.withInputValue(firstDiff.inputNumber, true) }; - } - - const result = combineInputs(baseRange, state.input2First ? 2 : 1); - if (result) { - return { edit: result, effectiveState: state }; - } - - return { - edit: secondDiff.diff.getLineEdit(), - effectiveState: ModifiedBaseRangeState.default.withInputValue( - secondDiff.inputNumber, - true - ), - }; -} - -function combineInputs(baseRange: ModifiedBaseRange, firstInput: 1 | 2): LineRangeEdit | undefined { - const combinedDiffs = concatArrays( - baseRange.input1Diffs.flatMap((diffs) => - diffs.rangeMappings.map((diff) => ({ diff, input: 1 as const })) - ), - baseRange.input2Diffs.flatMap((diffs) => - diffs.rangeMappings.map((diff) => ({ diff, input: 2 as const })) - ) - ).sort( - tieBreakComparators( - compareBy((d) => d.diff.inputRange, Range.compareRangesUsingStarts), - compareBy((d) => (d.input === firstInput ? 1 : 2), numberComparator) - ) - ); - - const sortedEdits = combinedDiffs.map(d => { - const sourceTextModel = d.input === 1 ? baseRange.input1TextModel : baseRange.input2TextModel; - return new RangeEdit(d.diff.inputRange, sourceTextModel.getValueInRange(d.diff.outputRange)); - }); - - return editsToLineRangeEdit(baseRange.baseRange, sortedEdits, baseRange.baseTextModel); -} - -function editsToLineRangeEdit(range: LineRange, sortedEdits: RangeEdit[], textModel: ITextModel): LineRangeEdit | undefined { - let text = ''; - const startsLineBefore = range.startLineNumber > 1; - let currentPosition = startsLineBefore - ? new Position( - range.startLineNumber - 1, - Constants.MAX_SAFE_SMALL_INTEGER - ) - : new Position(range.startLineNumber, 1); - - for (const edit of sortedEdits) { - const diffStart = edit.range.getStartPosition(); - if (!currentPosition.isBeforeOrEqual(diffStart)) { - return undefined; - } - let originalText = textModel.getValueInRange(Range.fromPositions(currentPosition, diffStart)); - if (diffStart.lineNumber > textModel.getLineCount()) { - // assert diffStart.lineNumber === textModel.getLineCount() + 1 - // getValueInRange doesn't include this virtual line break, as the document ends the line before. - // endsLineAfter will be false. - originalText += '\n'; - } - text += originalText; - text += edit.newText; - currentPosition = edit.range.getEndPosition(); - } - - const endsLineAfter = range.endLineNumberExclusive <= textModel.getLineCount(); - const end = endsLineAfter ? new Position( - range.endLineNumberExclusive, - 1 - ) : new Position(range.endLineNumberExclusive - 1, Constants.MAX_SAFE_SMALL_INTEGER); - - const originalText = textModel.getValueInRange( - Range.fromPositions(currentPosition, end) - ); - text += originalText; - - const lines = splitLines(text); - if (startsLineBefore) { - lines.shift(); - } - if (endsLineAfter) { - lines.pop(); - } - return new LineRangeEdit(range, lines); -} diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange.ts index 7befadb28bd..4dadbb6816a 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange.ts @@ -7,6 +7,13 @@ import { BugIndicatingError } from 'vs/base/common/errors'; import { ITextModel } from 'vs/editor/common/model'; import { DetailedLineRangeMapping, MappingAlignment } from 'vs/workbench/contrib/mergeEditor/browser/model/mapping'; import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange'; +import { tieBreakComparators, compareBy, numberComparator } from 'vs/base/common/arrays'; +import { splitLines } from 'vs/base/common/strings'; +import { Constants } from 'vs/base/common/uint'; +import { LineRangeEdit, RangeEdit } from 'vs/workbench/contrib/mergeEditor/browser/model/editing'; +import { concatArrays, elementAtOrUndefined } from 'vs/workbench/contrib/mergeEditor/browser/utils'; +import { Range } from 'vs/editor/common/core/range'; +import { Position } from 'vs/editor/common/core/position'; /** * Describes modifications in input 1 and input 2 for a specific range in base. @@ -72,8 +79,142 @@ export class ModifiedBaseRange { public get isConflicting(): boolean { return this.input1Diffs.length > 0 && this.input2Diffs.length > 0; } + + public get canBeCombined(): boolean { + return this.combineInputs(1) !== undefined; + } + + public get isOrderRelevant(): boolean { + const input1 = this.combineInputs(1); + const input2 = this.combineInputs(2); + if (!input1 || !input2) { + return false; + } + return !input1.equals(input2); + } + + public getEditForBase(state: ModifiedBaseRangeState): { edit: LineRangeEdit | undefined; effectiveState: ModifiedBaseRangeState } { + const diffs = concatArrays( + state.input1 && this.input1CombinedDiff ? [{ diff: this.input1CombinedDiff, inputNumber: 1 as const }] : [], + state.input2 && this.input2CombinedDiff ? [{ diff: this.input2CombinedDiff, inputNumber: 2 as const }] : [], + ); + + if (state.input2First) { + diffs.reverse(); + } + + const firstDiff = elementAtOrUndefined(diffs, 0); + const secondDiff = elementAtOrUndefined(diffs, 1); + + if (!firstDiff) { + return { edit: undefined, effectiveState: ModifiedBaseRangeState.default }; + } + if (!secondDiff) { + return { edit: firstDiff.diff.getLineEdit(), effectiveState: ModifiedBaseRangeState.default.withInputValue(firstDiff.inputNumber, true) }; + } + + const result = this.combineInputs(state.input2First ? 2 : 1); + if (result) { + return { edit: result, effectiveState: state }; + } + + return { + edit: secondDiff.diff.getLineEdit(), + effectiveState: ModifiedBaseRangeState.default.withInputValue( + secondDiff.inputNumber, + true + ), + }; + } + + private input1LineRangeEdit: LineRangeEdit | undefined | null = null; + private input2LineRangeEdit: LineRangeEdit | undefined | null = null; + + private combineInputs(firstInput: 1 | 2): LineRangeEdit | undefined { + if (firstInput === 1 && this.input1LineRangeEdit !== null) { + return this.input1LineRangeEdit; + } else if (firstInput === 2 && this.input2LineRangeEdit !== null) { + return this.input2LineRangeEdit; + } + + const combinedDiffs = concatArrays( + this.input1Diffs.flatMap((diffs) => + diffs.rangeMappings.map((diff) => ({ diff, input: 1 as const })) + ), + this.input2Diffs.flatMap((diffs) => + diffs.rangeMappings.map((diff) => ({ diff, input: 2 as const })) + ) + ).sort( + tieBreakComparators( + compareBy((d) => d.diff.inputRange, Range.compareRangesUsingStarts), + compareBy((d) => (d.input === firstInput ? 1 : 2), numberComparator) + ) + ); + + const sortedEdits = combinedDiffs.map(d => { + const sourceTextModel = d.input === 1 ? this.input1TextModel : this.input2TextModel; + return new RangeEdit(d.diff.inputRange, sourceTextModel.getValueInRange(d.diff.outputRange)); + }); + + const result = editsToLineRangeEdit(this.baseRange, sortedEdits, this.baseTextModel); + if (firstInput === 1) { + this.input1LineRangeEdit = result; + } else { + this.input2LineRangeEdit = result; + } + return result; + } } +function editsToLineRangeEdit(range: LineRange, sortedEdits: RangeEdit[], textModel: ITextModel): LineRangeEdit | undefined { + let text = ''; + const startsLineBefore = range.startLineNumber > 1; + let currentPosition = startsLineBefore + ? new Position( + range.startLineNumber - 1, + Constants.MAX_SAFE_SMALL_INTEGER + ) + : new Position(range.startLineNumber, 1); + + for (const edit of sortedEdits) { + const diffStart = edit.range.getStartPosition(); + if (!currentPosition.isBeforeOrEqual(diffStart)) { + return undefined; + } + let originalText = textModel.getValueInRange(Range.fromPositions(currentPosition, diffStart)); + if (diffStart.lineNumber > textModel.getLineCount()) { + // assert diffStart.lineNumber === textModel.getLineCount() + 1 + // getValueInRange doesn't include this virtual line break, as the document ends the line before. + // endsLineAfter will be false. + originalText += '\n'; + } + text += originalText; + text += edit.newText; + currentPosition = edit.range.getEndPosition(); + } + + const endsLineAfter = range.endLineNumberExclusive <= textModel.getLineCount(); + const end = endsLineAfter ? new Position( + range.endLineNumberExclusive, + 1 + ) : new Position(range.endLineNumberExclusive - 1, Constants.MAX_SAFE_SMALL_INTEGER); + + const originalText = textModel.getValueInRange( + Range.fromPositions(currentPosition, end) + ); + text += originalText; + + const lines = splitLines(text); + if (startsLineBefore) { + lines.shift(); + } + if (endsLineAfter) { + lines.pop(); + } + return new LineRangeEdit(range, lines); +} + + export class ModifiedBaseRangeState { public static readonly default = new ModifiedBaseRangeState(false, false, false, false); public static readonly conflicting = new ModifiedBaseRangeState(false, false, false, true); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts index 2b27e226680..74452e55aeb 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts @@ -119,10 +119,15 @@ export class InputCodeEditorView extends CodeEditorView { id: idx.toString(), range: baseRange.getInputRange(this.inputNumber), enabled: model.isUpToDate, - toggleState: derivedObservable('toggle', (reader) => model - .getState(baseRange) - .read(reader) - .getInput(this.inputNumber) + toggleState: derivedObservable('toggle', (reader) => { + const input = model + .getState(baseRange) + .read(reader) + .getInput(this.inputNumber); + return input === InputState.second && !baseRange.isOrderRelevant + ? InputState.first + : input; + } ), setState: (value, tx) => viewModel.setState( baseRange, @@ -177,7 +182,7 @@ export class InputCodeEditorView extends CodeEditorView { state.withInput1(!both).withInput2(!both), both ), - { enabled: true } // TODO + { enabled: baseRange.canBeCombined } ) : undefined, baseRange.isConflicting @@ -188,7 +193,7 @@ export class InputCodeEditorView extends CodeEditorView { state.swap(), false ), - { enabled: !state.isEmpty } + { enabled: !state.isEmpty && (!both || baseRange.isOrderRelevant) } ) : undefined, From 7aec222831a757d6013a5eb74c026d967e87ac2b Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 29 Jun 2022 14:20:49 +0200 Subject: [PATCH 059/150] joh/issue150862 (#153677) * execute `workbench.view.scm` to reval SCM view after accepting merge fixes https://github.com/microsoft/vscode/issues/150862 * only reveal viewlet when stage/accept was done --- extensions/git/src/commands.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 564206bfa58..6a7670baacc 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -1109,6 +1109,7 @@ export class CommandCenter { // TODO@jrieken there isn't a `TabInputTextMerge` instance yet, till now the merge editor // uses the `TabInputText` for the out-resource and we use that to identify and CLOSE the tab + // see https://github.com/microsoft/vscode/issues/153213 const { activeTab } = window.tabGroups.activeTabGroup; let didCloseTab = false; if (activeTab && activeTab?.input instanceof TabInputText && activeTab.input.uri.toString() === uri.toString()) { @@ -1119,6 +1120,7 @@ export class CommandCenter { // handled or unhandled conflicts are OK by the user. if (didCloseTab) { await repository.add([uri]); + await commands.executeCommand('workbench.view.scm'); } } From 4595b6cc7c11953d90cfe6fd751cc5608a643427 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 29 Jun 2022 14:25:27 +0200 Subject: [PATCH 060/150] Fixes #153495. --- src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts index ede54697d62..b53549fea4b 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts @@ -92,7 +92,7 @@ export class MergeEditorViewModel { const range = this.getRange(lastFocusedEditor, modifiedBaseRange, undefined); lastFocusedEditor.editor.setPosition({ lineNumber: range.startLineNumber, - column: 1, + column: lastFocusedEditor.editor.getModel()!.getLineFirstNonWhitespaceColumn(range.startLineNumber), }); lastFocusedEditor.editor.revealLinesNearTop(range.startLineNumber, range.endLineNumberExclusive, ScrollType.Smooth); } From 1805c26b871c2c94f0e8a4fdc50bb4a3f6c0a9a9 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 29 Jun 2022 14:36:51 +0200 Subject: [PATCH 061/150] Fixes #153186. --- .../contrib/mergeEditor/browser/view/mergeEditor.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts index a472b1110b4..0a5470562e4 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts @@ -142,6 +142,10 @@ export class MergeEditor extends AbstractTextEditor { synchronizeScrolling(this.input1View.editor, this.inputResultView.editor, mapping, MappingDirection.input); this.input2View.editor.setScrollTop(c.scrollTop, ScrollType.Immediate); } + if (c.scrollLeftChanged) { + this.input2View.editor.setScrollLeft(c.scrollLeft, ScrollType.Immediate); + this.inputResultView.editor.setScrollLeft(c.scrollLeft, ScrollType.Immediate); + } }) ) ); @@ -153,6 +157,10 @@ export class MergeEditor extends AbstractTextEditor { synchronizeScrolling(this.input2View.editor, this.inputResultView.editor, mapping, MappingDirection.input); this.input1View.editor.setScrollTop(c.scrollTop, ScrollType.Immediate); } + if (c.scrollLeftChanged) { + this.input1View.editor.setScrollLeft(c.scrollLeft, ScrollType.Immediate); + this.inputResultView.editor.setScrollLeft(c.scrollLeft, ScrollType.Immediate); + } }) ) ); @@ -165,6 +173,10 @@ export class MergeEditor extends AbstractTextEditor { const mapping2 = this.model?.input2ResultMapping.get(); synchronizeScrolling(this.inputResultView.editor, this.input2View.editor, mapping2, MappingDirection.output); } + if (c.scrollLeftChanged) { + this.input1View.editor.setScrollLeft(c.scrollLeft, ScrollType.Immediate); + this.input2View.editor.setScrollLeft(c.scrollLeft, ScrollType.Immediate); + } }) ) ); From 8045df1b9423e233a79306a90257c28fc7e39a4e Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 29 Jun 2022 16:00:09 +0200 Subject: [PATCH 062/150] Git - Add commands to the editor title to accept/discard commit message (#153692) Add commands to the editor title to accept/discard commit message --- extensions/git/package.json | 30 ++++++++++++++++++++++ extensions/git/package.nls.json | 2 ++ extensions/git/src/commands.ts | 45 +++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+) diff --git a/extensions/git/package.json b/extensions/git/package.json index 32c70fed3b7..3e706e0ec6a 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -307,6 +307,18 @@ "category": "Git", "enablement": "!commitInProgress" }, + { + "command": "git.commitMessageAccept", + "title": "%command.commitMessageAccept%", + "icon": "$(check)", + "category": "Git" + }, + { + "command": "git.commitMessageDiscard", + "title": "%command.commitMessageDiscard%", + "icon": "$(discard)", + "category": "Git" + }, { "command": "git.restoreCommitTemplate", "title": "%command.restoreCommitTemplate%", @@ -796,6 +808,14 @@ "command": "git.restoreCommitTemplate", "when": "false" }, + { + "command": "git.commitMessageAccept", + "when": "false" + }, + { + "command": "git.commitMessageDiscard", + "when": "false" + }, { "command": "git.revealInExplorer", "when": "false" @@ -1481,6 +1501,16 @@ "group": "navigation", "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && !isInDiffEditor && !isMergeEditor && resourceScheme == file && scmActiveResourceHasChanges" }, + { + "command": "git.commitMessageAccept", + "group": "navigation", + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && editorLangId == git-commit && commitInProgress" + }, + { + "command": "git.commitMessageDiscard", + "group": "navigation", + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && editorLangId == git-commit && commitInProgress" + }, { "command": "git.stageSelectedRanges", "group": "2_git@1", diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 2d6c88e59e1..c5b11a7b85a 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -46,6 +46,8 @@ "command.commitAllNoVerify": "Commit All (No Verify)", "command.commitAllSignedNoVerify": "Commit All (Signed Off, No Verify)", "command.commitAllAmendNoVerify": "Commit All (Amend, No Verify)", + "command.commitMessageAccept": "Accept Commit Message", + "command.commitMessageDiscard": "Discard Commit Message", "command.restoreCommitTemplate": "Restore Commit Template", "command.undoCommit": "Undo Last Commit", "command.checkout": "Checkout to...", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 6a7670baacc..052f266eedd 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -1711,6 +1711,51 @@ export class CommandCenter { await this.commitWithAnyInput(repository, { all: true, amend: true }); } + @command('git.commitMessageAccept') + async commitMessageAccept(arg?: Uri): Promise { + if (!arg) { return; } + + // Close the tab + this._closeEditorTab(arg); + } + + @command('git.commitMessageDiscard') + async commitMessageDiscard(arg?: Uri): Promise { + if (!arg) { return; } + + // Clear the contents of the editor + const editors = window.visibleTextEditors + .filter(e => e.document.languageId === 'git-commit' && e.document.uri.toString() === arg.toString()); + + if (editors.length !== 1) { return; } + + const commitMsgEditor = editors[0]; + const commitMsgDocument = commitMsgEditor.document; + + const editResult = await commitMsgEditor.edit(builder => { + const firstLine = commitMsgDocument.lineAt(0); + const lastLine = commitMsgDocument.lineAt(commitMsgDocument.lineCount - 1); + + builder.delete(new Range(firstLine.range.start, lastLine.range.end)); + }); + + if (!editResult) { return; } + + // Save the document + const saveResult = await commitMsgDocument.save(); + if (!saveResult) { return; } + + // Close the tab + this._closeEditorTab(arg); + } + + private _closeEditorTab(uri: Uri): void { + const tabToClose = window.tabGroups.all.map(g => g.tabs).flat() + .filter(t => t.input instanceof TabInputText && t.input.uri.toString() === uri.toString()); + + window.tabGroups.close(tabToClose); + } + private async _commitEmpty(repository: Repository, noVerify?: boolean): Promise { const root = Uri.file(repository.root); const config = workspace.getConfiguration('git', root); From b4a97a63a998e2d6b67f3ff521e3baf96bb7233e Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 29 Jun 2022 16:36:12 +0200 Subject: [PATCH 063/150] Adds precondition to merge editor commands, uses merge editor context constant. --- .../mergeEditor/browser/commands/commands.ts | 20 +++++++++++++------ .../browser/commands/devCommands.ts | 8 ++++++-- .../mergeEditor/browser/view/mergeEditor.ts | 9 ++------- .../contrib/mergeEditor/common/mergeEditor.ts | 12 +++++++++++ .../userDataSync/browser/userDataSync.ts | 5 +++-- 5 files changed, 37 insertions(+), 17 deletions(-) create mode 100644 src/vs/workbench/contrib/mergeEditor/common/mergeEditor.ts diff --git a/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts b/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts index 8814e2435be..8d4d6ad1e96 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts @@ -8,11 +8,11 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { Action2, MenuId } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { API_OPEN_DIFF_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; import { MergeEditorInput, MergeEditorInputData } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; -import { ctxIsMergeEditor, ctxMergeEditorLayout, MergeEditor } from 'vs/workbench/contrib/mergeEditor/browser/view/mergeEditor'; +import { MergeEditor } from 'vs/workbench/contrib/mergeEditor/browser/view/mergeEditor'; +import { ctxMergeEditorLayout, ctxIsMergeEditor } from 'vs/workbench/contrib/mergeEditor/common/mergeEditor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; export class OpenMergeEditor extends Action2 { @@ -112,13 +112,14 @@ export class SetMixedLayout extends Action2 { super({ id: 'merge.mixedLayout', title: localize('layout.mixed', "Mixed Layout"), - toggled: ContextKeyExpr.equals(ctxMergeEditorLayout.key, 'mixed'), + toggled: ctxMergeEditorLayout.isEqualTo('mixed'), menu: [{ id: MenuId.EditorTitle, when: ctxIsMergeEditor, group: '1_merge', order: 9, - }] + }], + precondition: ctxIsMergeEditor, }); } @@ -135,13 +136,14 @@ export class SetColumnLayout extends Action2 { super({ id: 'merge.columnLayout', title: localize('layout.column', "Column Layout"), - toggled: ContextKeyExpr.equals(ctxMergeEditorLayout.key, 'columns'), + toggled: ctxMergeEditorLayout.isEqualTo('columns'), menu: [{ id: MenuId.EditorTitle, when: ctxIsMergeEditor, group: '1_merge', order: 10, - }] + }], + precondition: ctxIsMergeEditor, }); } @@ -166,6 +168,7 @@ export class GoToNextConflict extends Action2 { group: 'navigation', }], f1: true, + precondition: ctxIsMergeEditor, }); } @@ -190,6 +193,7 @@ export class GoToPreviousConflict extends Action2 { group: 'navigation', }], f1: true, + precondition: ctxIsMergeEditor, }); } @@ -208,6 +212,7 @@ export class ToggleActiveConflictInput1 extends Action2 { category: localize('mergeEditor', "Merge Editor"), title: localize('merge.toggleActiveConflictInput1', "Toggle Active Conflict In Input 1"), f1: true, + precondition: ctxIsMergeEditor, }); } @@ -230,6 +235,7 @@ export class ToggleActiveConflictInput2 extends Action2 { category: localize('mergeEditor', "Merge Editor"), title: localize('merge.toggleActiveConflictInput2', "Toggle Active Conflict In Input 2"), f1: true, + precondition: ctxIsMergeEditor, }); } @@ -252,6 +258,7 @@ export class CompareInput1WithBaseCommand extends Action2 { category: localize('mergeEditor', "Merge Editor"), title: localize('mergeEditor.compareInput1WithBase', "Compare Input 1 With Base"), f1: true, + precondition: ctxIsMergeEditor, }); } run(accessor: ServicesAccessor, ...args: unknown[]): void { @@ -268,6 +275,7 @@ export class CompareInput2WithBaseCommand extends Action2 { category: localize('mergeEditor', "Merge Editor"), title: localize('mergeEditor.compareInput2WithBase', "Compare Input 2 With Base"), f1: true, + precondition: ctxIsMergeEditor, }); } run(accessor: ServicesAccessor, ...args: unknown[]): void { diff --git a/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts b/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts index 1ca1ed09565..83a458d15f5 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts @@ -18,6 +18,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IWorkbenchFileService } from 'vs/workbench/services/files/common/files'; import { URI } from 'vs/base/common/uri'; import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; +import { ctxIsMergeEditor } from 'vs/workbench/contrib/mergeEditor/common/mergeEditor'; interface MergeEditorContents { languageId: string; @@ -32,9 +33,11 @@ export class MergeEditorCopyContentsToJSON extends Action2 { constructor() { super({ id: 'merge.dev.copyContents', - title: localize('merge.dev.copyContents', "Developer Merge Editor: Copy Contents of Inputs, Base and Result as JSON"), + category: 'Merge Editor (Dev)', + title: localize('merge.dev.copyContents', "Copy Contents of Inputs, Base and Result as JSON"), icon: Codicon.layoutCentered, f1: true, + precondition: ctxIsMergeEditor, }); } @@ -76,7 +79,8 @@ export class MergeEditorOpenContents extends Action2 { constructor() { super({ id: 'merge.dev.openContents', - title: localize('merge.dev.openContents', "Developer Merge Editor: Open Contents of Inputs, Base and Result from JSON"), + category: 'Merge Editor (Dev)', + title: localize('merge.dev.openContents', "Open Contents of Inputs, Base and Result from JSON"), icon: Codicon.layoutCentered, f1: true, }); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts index 0a5470562e4..ae2fcbcd7f1 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts @@ -23,7 +23,7 @@ import { localize } from 'vs/nls'; import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -42,6 +42,7 @@ import { DocumentMapping, getOppositeDirection, MappingDirection } from 'vs/work import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel'; import { deepMerge, ReentrancyBarrier, thenIfNotDisposed } from 'vs/workbench/contrib/mergeEditor/browser/utils'; import { MergeEditorViewModel } from 'vs/workbench/contrib/mergeEditor/browser/view/viewModel'; +import { ctxBaseResourceScheme, ctxIsMergeEditor, ctxMergeEditorLayout, MergeEditorLayoutTypes } from 'vs/workbench/contrib/mergeEditor/common/mergeEditor'; import { settingsSashBorder } from 'vs/workbench/contrib/preferences/common/settingsEditorColorRegistry'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService'; @@ -50,12 +51,6 @@ import './colors'; import { InputCodeEditorView } from './editors/inputCodeEditorView'; import { ResultCodeEditorView } from './editors/resultCodeEditorView'; -export const ctxIsMergeEditor = new RawContextKey('isMergeEditor', false); -export const ctxMergeEditorLayout = new RawContextKey('mergeEditorLayout', 'mixed'); -export const ctxBaseResourceScheme = new RawContextKey('baseResourceScheme', ''); - -export type MergeEditorLayoutTypes = 'mixed' | 'columns'; - class MergeEditorLayout { private static readonly _key = 'mergeEditor/layout'; diff --git a/src/vs/workbench/contrib/mergeEditor/common/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/common/mergeEditor.ts new file mode 100644 index 00000000000..9ec46266ae2 --- /dev/null +++ b/src/vs/workbench/contrib/mergeEditor/common/mergeEditor.ts @@ -0,0 +1,12 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; + +export type MergeEditorLayoutTypes = 'mixed' | 'columns'; + +export const ctxIsMergeEditor = new RawContextKey('isMergeEditor', false); +export const ctxMergeEditorLayout = new RawContextKey('mergeEditorLayout', 'mixed'); +export const ctxBaseResourceScheme = new RawContextKey('baseResourceScheme', ''); diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index a406183a623..9f113cfabab 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -18,7 +18,7 @@ import { localize } from 'vs/nls'; import { MenuId, MenuRegistry, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ContextKeyDefinedExpr, ContextKeyEqualsExpr, ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyEqualsExpr, ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; @@ -59,6 +59,7 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { ctxIsMergeEditor } from 'vs/workbench/contrib/mergeEditor/common/mergeEditor'; const CONTEXT_CONFLICTS_SOURCES = new RawContextKey('conflictsSources', ''); @@ -1290,7 +1291,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo title: localize('accept merges title', "Accept Merge"), menu: [{ id: MenuId.MergeToolbar, - when: ContextKeyExpr.and(ContextKeyDefinedExpr.create('isMergeEditor'), ContextKeyEqualsExpr.create('baseResourceScheme', USER_DATA_SYNC_SCHEME)), + when: ContextKeyExpr.and(ctxIsMergeEditor, ContextKeyEqualsExpr.create('baseResourceScheme', USER_DATA_SYNC_SCHEME)), }], }); } From 7fb4287727b8fa89df881a71ae9e69e840a27731 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 29 Jun 2022 16:37:23 +0200 Subject: [PATCH 064/150] calling `executeCommand` for a locally known extension cmd should fire activation event (#153683) We should always fire the event but when the command is already known (locally) we don't await that. fixes https://github.com/microsoft/vscode/issues/150293 --- .../api/browser/mainThreadCommands.ts | 9 +++++++ .../workbench/api/common/extHost.protocol.ts | 1 + .../workbench/api/common/extHostCommands.ts | 7 ++++-- .../api/test/browser/extHostCommands.test.ts | 24 +++++++++++++++++++ 4 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadCommands.ts b/src/vs/workbench/api/browser/mainThreadCommands.ts index b1500fcbe64..0321d2202f5 100644 --- a/src/vs/workbench/api/browser/mainThreadCommands.ts +++ b/src/vs/workbench/api/browser/mainThreadCommands.ts @@ -74,6 +74,15 @@ export class MainThreadCommands implements MainThreadCommandsShape { } } + $fireCommandActivationEvent(id: string): void { + const activationEvent = `onCommand:${id}`; + if (!this._extensionService.activationEventIsDone(activationEvent)) { + // this is NOT awaited because we only use it as drive-by-activation + // for commands that are already known inside the extension host + this._extensionService.activateByEvent(activationEvent); + } + } + async $executeCommand(id: string, args: any[] | SerializableObjectWithBuffers, retry: boolean): Promise { if (args instanceof SerializableObjectWithBuffers) { args = args.value; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 5af7bb15f92..a7b203695df 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -92,6 +92,7 @@ export interface MainThreadClipboardShape extends IDisposable { export interface MainThreadCommandsShape extends IDisposable { $registerCommand(id: string): void; $unregisterCommand(id: string): void; + $fireCommandActivationEvent(id: string): void; $executeCommand(id: string, args: any[] | SerializableObjectWithBuffers, retry: boolean): Promise; $getCommands(): Promise; } diff --git a/src/vs/workbench/api/common/extHostCommands.ts b/src/vs/workbench/api/common/extHostCommands.ts index 109e515a327..62c9a3c5c9b 100644 --- a/src/vs/workbench/api/common/extHostCommands.ts +++ b/src/vs/workbench/api/common/extHostCommands.ts @@ -169,8 +169,11 @@ export class ExtHostCommands implements ExtHostCommandsShape { private async _doExecuteCommand(id: string, args: any[], retry: boolean): Promise { if (this._commands.has(id)) { - // we stay inside the extension host and support - // to pass any kind of parameters around + // - We stay inside the extension host and support + // to pass any kind of parameters around. + // - We still emit the corresponding activation event + // BUT we don't await that event + this.#proxy.$fireCommandActivationEvent(id); return this._executeContributedCommand(id, args, false); } else { diff --git a/src/vs/workbench/api/test/browser/extHostCommands.test.ts b/src/vs/workbench/api/test/browser/extHostCommands.test.ts index da9b47ccda9..e38f919bee8 100644 --- a/src/vs/workbench/api/test/browser/extHostCommands.test.ts +++ b/src/vs/workbench/api/test/browser/extHostCommands.test.ts @@ -90,4 +90,28 @@ suite('ExtHostCommands', function () { assert.strictEqual(result, 17); assert.strictEqual(count, 2); }); + + test('onCommand:abc activates extensions when executed from command palette, but not when executed programmatically with vscode.commands.executeCommand #150293', async function () { + + const activationEvents: string[] = []; + + const shape = new class extends mock() { + override $registerCommand(id: string): void { + // + } + override $fireCommandActivationEvent(id: string): void { + activationEvents.push(id); + } + }; + const commands = new ExtHostCommands( + SingleProxyRPCProtocol(shape), + new NullLogService() + ); + + commands.registerCommand(true, 'extCmd', (args: any): any => args); + + const result = await commands.executeCommand('extCmd', this); + assert.strictEqual(result, this); + assert.deepStrictEqual(activationEvents, ['extCmd']); + }); }); From da34523828257897a992bdd909badb19570a951b Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 29 Jun 2022 16:55:42 +0200 Subject: [PATCH 065/150] durationSinceUpdate should be in seconds --- .../common/abstractExtensionManagementService.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts index 5e5353eed1f..66e250a2df1 100644 --- a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts +++ b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts @@ -224,10 +224,11 @@ export abstract class AbstractExtensionManagementService extends Disposable impl await this.joinAllSettled(this.participants.map(participant => participant.postInstall(local, task.source, options, CancellationToken.None))); if (!URI.isUri(task.source)) { const isUpdate = task.operation === InstallOperation.Update; + const durationSinceUpdate = isUpdate ? undefined : (new Date().getTime() - task.source.lastUpdated) / 1000; reportTelemetry(this.telemetryService, isUpdate ? 'extensionGallery:update' : 'extensionGallery:install', { extensionData: getGalleryExtensionTelemetryData(task.source), duration: new Date().getTime() - startTime, - durationSinceUpdate: isUpdate ? undefined : new Date().getTime() - task.source.lastUpdated + durationSinceUpdate }); // In web, report extension install statistics explicitly. In Desktop, statistics are automatically updated while downloading the VSIX. if (isWeb && task.operation !== InstallOperation.Update) { From 0a45975f7b758a37b606042cea507ed57d643dbd Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Wed, 29 Jun 2022 07:57:57 -0700 Subject: [PATCH 066/150] Shorten Settings editor aria labels (#153625) Fixes #153588 --- .../browser/settingsEditorSettingIndicators.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts index 44bb7b7afd8..b3c8f7ffec6 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts @@ -290,21 +290,21 @@ export function getIndicatorsLabelAriaLabel(element: SettingsTreeSettingElement, // Add sync ignored text const ignoredSettings = getIgnoredSettings(getDefaultIgnoredSettings(), configurationService); if (ignoredSettings.includes(element.setting.key)) { - ariaLabelSections.push(localize('syncIgnoredTitle', "This setting is ignored during sync")); + ariaLabelSections.push(localize('syncIgnoredAriaLabel', "Setting ignored during sync")); } // Add default override indicator text const sourceToDisplay = getDefaultValueSourceToDisplay(element); if (sourceToDisplay !== undefined) { - ariaLabelSections.push(localize('defaultOverriddenDetails', "Default setting value overridden by {0}", sourceToDisplay)); + ariaLabelSections.push(localize('defaultOverriddenDetailsAriaLabel', "{0} overrides the default value", sourceToDisplay)); } // Add text about default values being overridden in other languages - const otherLanguageOverridesStart = localize('defaultOverriddenListPreface', "The default value of the setting has also been overridden for the following languages:"); const otherLanguageOverridesList = element.overriddenDefaultsLanguageList .map(language => languageService.getLanguageName(language)).join(', '); if (element.overriddenDefaultsLanguageList.length) { - ariaLabelSections.push(`${otherLanguageOverridesStart} ${otherLanguageOverridesList}`); + const otherLanguageOverridesText = localize('defaultOverriddenLanguagesList', "Language-specific default values exist for {0}", otherLanguageOverridesList); + ariaLabelSections.push(otherLanguageOverridesText); } const ariaLabel = ariaLabelSections.join('. '); From 9fb10318e7cf0c55c8c9436f1d75fae16f591f82 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 29 Jun 2022 16:58:22 +0200 Subject: [PATCH 067/150] Fix #153650 (#153700) --- .../workbench/services/keybinding/browser/keybindingService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/services/keybinding/browser/keybindingService.ts b/src/vs/workbench/services/keybinding/browser/keybindingService.ts index 2317c4e28fa..ccd362686a0 100644 --- a/src/vs/workbench/services/keybinding/browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/browser/keybindingService.ts @@ -740,7 +740,7 @@ class UserKeybindings extends Disposable { this._register(userDataProfileService.onDidChangeCurrentProfile(e => { this.watch(); - e.join(this.reload().then(() => this.reloadConfigurationScheduler.schedule())); + this.reloadConfigurationScheduler.schedule(); })); } From db56bde061f59a58d00066846dca30a5a59d89a3 Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Wed, 29 Jun 2022 08:02:49 -0700 Subject: [PATCH 068/150] Remove info icons from Settings editor indicators + other small fixes (#153621) Fixes #153578 Fixes #153576 Fixes #153420 Fixes #153574 Fixes #153586 Fixes #153638 - Remove info icons - Add underline when hovering over indicators - Handle sync ignored hover properly --- .../preferences/browser/media/settingsEditor2.css | 6 ++++++ .../browser/settingsEditorSettingIndicators.ts | 12 +++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css index ff06cba0a11..d44eec7e5d5 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css +++ b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css @@ -351,6 +351,12 @@ font-style: italic; } +.settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-title > .misc-label .setting-item-overrides:hover, +.settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-title > .misc-label .setting-item-ignored:hover, +.settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-title > .misc-label .setting-item-default-overridden:hover { + text-decoration: underline; +} + .settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-title .codicon { vertical-align: middle; padding-left: 1px; diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts index b3c8f7ffec6..a10179c4ac7 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts @@ -37,6 +37,7 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { private scopeOverridesElement: HTMLElement; private scopeOverridesLabel: SimpleIconLabel; private syncIgnoredElement: HTMLElement; + private syncIgnoredHover: ICustomHover | undefined; private defaultOverrideIndicatorElement: HTMLElement; private hoverDelegate: IHoverDelegate; private hover: ICustomHover | undefined; @@ -60,6 +61,7 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { showHover: (options: IHoverDelegateOptions, focus?: boolean) => { return hoverService.showHover(options, focus); }, + onDidHideHover: () => { }, delay: configurationService.getValue('workbench.hover.delay'), placement: 'element' }; @@ -74,16 +76,16 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { private createSyncIgnoredElement(): HTMLElement { const syncIgnoredElement = $('span.setting-item-ignored'); const syncIgnoredLabel = new SimpleIconLabel(syncIgnoredElement); - syncIgnoredLabel.text = '$(info) ' + localize('extensionSyncIgnoredLabel', 'Not synced'); + syncIgnoredLabel.text = localize('extensionSyncIgnoredLabel', 'Not synced'); const syncIgnoredHoverContent = localize('syncIgnoredTitle', "This setting is ignored during sync"); - setupCustomHover(this.hoverDelegate, syncIgnoredElement, syncIgnoredHoverContent); + this.syncIgnoredHover = setupCustomHover(this.hoverDelegate, syncIgnoredElement, syncIgnoredHoverContent); return syncIgnoredElement; } private createDefaultOverrideIndicator(): HTMLElement { const defaultOverrideIndicator = $('span.setting-item-default-overridden'); const defaultOverrideLabel = new SimpleIconLabel(defaultOverrideIndicator); - defaultOverrideLabel.text = '$(info) ' + localize('defaultOverriddenLabel', "Default value changed"); + defaultOverrideLabel.text = localize('defaultOverriddenLabel', "Default value changed"); return defaultOverrideIndicator; } @@ -125,6 +127,7 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { dispose() { this.hover?.dispose(); + this.syncIgnoredHover?.dispose(); } updateScopeOverrides(element: SettingsTreeSettingElement, elementDisposables: DisposableStore, onDidClickOverrideElement: Emitter) { @@ -167,8 +170,7 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { // show the text in a custom hover only if // the feature flag isn't on. this.scopeOverridesElement.style.display = 'inline'; - let scopeOverridesLabelText = '$(info) '; - scopeOverridesLabelText += element.isConfigured ? + const scopeOverridesLabelText = element.isConfigured ? localize('alsoConfiguredElsewhere', "Also modified elsewhere") : localize('configuredElsewhere', "Modified elsewhere"); this.scopeOverridesLabel.text = scopeOverridesLabelText; From b833280de5740c8929930658f3033c667f01100a Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Wed, 29 Jun 2022 08:19:44 -0700 Subject: [PATCH 069/150] Remove State from Debug Console Quick Access (#153626) Remove status on debug console quick access --- .../workbench/contrib/debug/browser/debugConsoleQuickAccess.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugConsoleQuickAccess.ts b/src/vs/workbench/contrib/debug/browser/debugConsoleQuickAccess.ts index 370e400370f..5479a5a393c 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConsoleQuickAccess.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConsoleQuickAccess.ts @@ -11,7 +11,7 @@ import { FastAndSlowPicks, IPickerQuickAccessItem, PickerQuickAccessProvider, Pi import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { IViewsService } from 'vs/workbench/common/views'; import { DEBUG_CONSOLE_QUICK_ACCESS_PREFIX, SELECT_AND_START_ID } from 'vs/workbench/contrib/debug/browser/debugCommands'; -import { getStateLabel, IDebugService, IDebugSession, REPL_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugService, IDebugSession, REPL_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug'; export class DebugConsoleQuickAccess extends PickerQuickAccessProvider { @@ -54,7 +54,6 @@ export class DebugConsoleQuickAccess extends PickerQuickAccessProvider { this._debugService.focusStackFrame(undefined, undefined, session, { explicit: true }); From 0213a6d51a1228c6c4e3be1162090b46b76120bd Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 29 Jun 2022 17:29:42 +0200 Subject: [PATCH 070/150] Fix #153655 (#153703) --- src/vs/platform/storage/common/storage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/storage/common/storage.ts b/src/vs/platform/storage/common/storage.ts index 570f41553a6..de09d638262 100644 --- a/src/vs/platform/storage/common/storage.ts +++ b/src/vs/platform/storage/common/storage.ts @@ -555,7 +555,7 @@ export abstract class AbstractStorageService extends Disposable implements IStor return false; // both profiles are same } - if (isProfileUsingDefaultStorage(to) === isProfileUsingDefaultStorage(from)) { + if (isProfileUsingDefaultStorage(to) && isProfileUsingDefaultStorage(from)) { return false; // both profiles are using default } From 230ebd4812c239fd51c0f3786bfd9fe28b8ba8d9 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 29 Jun 2022 17:59:36 +0200 Subject: [PATCH 071/150] Udpate classifier.json for task ownership (#153709) --- .github/classifier.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/classifier.json b/.github/classifier.json index 5b5625f6e93..9e7e6e26e17 100644 --- a/.github/classifier.json +++ b/.github/classifier.json @@ -145,7 +145,7 @@ "snippets": {"assign": ["jrieken"]}, "splitview": {"assign": ["joaomoreno"]}, "suggest": {"assign": ["jrieken"]}, - "tasks": {"assign": ["alexr00"], "accuracy": 0.85}, + "tasks": {"assign": ["meganrogge"], "accuracy": 0.85}, "telemetry": {"assign": []}, "themes": {"assign": ["aeschli"]}, "timeline": {"assign": ["lramos15"]}, From a9f9ed2f917387342a7a1ff680ccba2b0ce05515 Mon Sep 17 00:00:00 2001 From: SteVen Batten <6561887+sbatten@users.noreply.github.com> Date: Wed, 29 Jun 2022 10:27:50 -0700 Subject: [PATCH 072/150] Updates storage scopes for trustedDomains (#153723) refs #152679 --- src/vs/workbench/contrib/url/browser/trustedDomains.ts | 6 +++--- .../contrib/url/browser/trustedDomainsFileSystemProvider.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/url/browser/trustedDomains.ts b/src/vs/workbench/contrib/url/browser/trustedDomains.ts index 44ec35f83f8..2019b04e521 100644 --- a/src/vs/workbench/contrib/url/browser/trustedDomains.ts +++ b/src/vs/workbench/contrib/url/browser/trustedDomains.ts @@ -112,11 +112,11 @@ export async function configureOpenerTrustedDomainsHandler( case 'trust': { const itemToTrust = pickedResult.toTrust; if (trustedDomains.indexOf(itemToTrust) === -1) { - storageService.remove(TRUSTED_DOMAINS_CONTENT_STORAGE_KEY, StorageScope.PROFILE); + storageService.remove(TRUSTED_DOMAINS_CONTENT_STORAGE_KEY, StorageScope.APPLICATION); storageService.store( TRUSTED_DOMAINS_STORAGE_KEY, JSON.stringify([...trustedDomains, itemToTrust]), - StorageScope.PROFILE, + StorageScope.APPLICATION, StorageTarget.USER ); @@ -214,7 +214,7 @@ export function readStaticTrustedDomains(accessor: ServicesAccessor): IStaticTru let trustedDomains: string[] = []; try { - const trustedDomainsSrc = storageService.get(TRUSTED_DOMAINS_STORAGE_KEY, StorageScope.PROFILE); + const trustedDomainsSrc = storageService.get(TRUSTED_DOMAINS_STORAGE_KEY, StorageScope.APPLICATION); if (trustedDomainsSrc) { trustedDomains = JSON.parse(trustedDomainsSrc); } diff --git a/src/vs/workbench/contrib/url/browser/trustedDomainsFileSystemProvider.ts b/src/vs/workbench/contrib/url/browser/trustedDomainsFileSystemProvider.ts index a80ff1ec236..c13d93e6721 100644 --- a/src/vs/workbench/contrib/url/browser/trustedDomainsFileSystemProvider.ts +++ b/src/vs/workbench/contrib/url/browser/trustedDomainsFileSystemProvider.ts @@ -109,7 +109,7 @@ export class TrustedDomainsFileSystemProvider implements IFileSystemProviderWith async readFile(resource: URI): Promise { let trustedDomainsContent = this.storageService.get( TRUSTED_DOMAINS_CONTENT_STORAGE_KEY, - StorageScope.PROFILE + StorageScope.APPLICATION ); const configuring: string | undefined = resource.fragment; @@ -134,11 +134,11 @@ export class TrustedDomainsFileSystemProvider implements IFileSystemProviderWith const trustedDomainsContent = VSBuffer.wrap(content).toString(); const trustedDomains = parse(trustedDomainsContent); - this.storageService.store(TRUSTED_DOMAINS_CONTENT_STORAGE_KEY, trustedDomainsContent, StorageScope.PROFILE, StorageTarget.USER); + this.storageService.store(TRUSTED_DOMAINS_CONTENT_STORAGE_KEY, trustedDomainsContent, StorageScope.APPLICATION, StorageTarget.USER); this.storageService.store( TRUSTED_DOMAINS_STORAGE_KEY, JSON.stringify(trustedDomains) || '', - StorageScope.PROFILE, + StorageScope.APPLICATION, StorageTarget.USER ); } catch (err) { } From 004319fbdccfa013800f6b81ab4816174d7c0a2f Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 29 Jun 2022 13:36:49 -0400 Subject: [PATCH 073/150] show icon/color in default build task quickpick (#153705) --- .../tasks/browser/abstractTaskService.ts | 67 ++++++++++++++----- 1 file changed, 49 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index b421bbe2913..9ef8982eebe 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -3272,27 +3272,58 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer this._runConfigureTasks(); return; } + const entries: QuickPickInput[] = []; let selectedTask: Task | undefined; - let selectedEntry: ITaskQuickPickEntry; - for (const task of tasks) { - const taskGroup: TaskGroup | undefined = TaskGroup.from(task.configurationProperties.group); - if (taskGroup && taskGroup.isDefault && taskGroup._id === TaskGroup.Build._id) { - selectedTask = task; - break; - } - } - if (selectedTask) { - selectedEntry = { - label: nls.localize('TaskService.defaultBuildTaskExists', '{0} is already marked as the default build task', selectedTask.getQualifiedLabel()), - task: selectedTask, - detail: this._showDetail() ? selectedTask.configurationProperties.detail : undefined - }; - } + let selectedEntry: TaskQuickPickEntryType | undefined; this._showIgnoredFoldersMessage().then(() => { - this._showQuickPick(tasks, - nls.localize('TaskService.pickDefaultBuildTask', 'Select the task to be used as the default build task'), undefined, true, false, selectedEntry). + for (const task of tasks) { + const taskGroup: TaskGroup | undefined = TaskGroup.from(task.configurationProperties.group); + if (taskGroup && taskGroup.isDefault && taskGroup._id === TaskGroup.Build._id) { + const label = nls.localize('TaskService.defaultBuildTaskExists', '{0} is already marked as the default build task', TaskQuickPick.getTaskLabelWithIcon(task, task.getQualifiedLabel())); + selectedTask = task; + selectedEntry = { label, task, description: this.getTaskDescription(task), detail: this._showDetail() ? task.configurationProperties.detail : undefined }; + TaskQuickPick.applyColorStyles(task, selectedEntry, this._themeService); + } else { + const entry = { label: TaskQuickPick.getTaskLabelWithIcon(task), task, description: this.getTaskDescription(task), detail: this._showDetail() ? task.configurationProperties.detail : undefined }; + TaskQuickPick.applyColorStyles(task, entry, this._themeService); + entries.push(entry); + } + } + if (selectedEntry) { + entries.unshift(selectedEntry); + } + const tokenSource = new CancellationTokenSource(); + const cancellationToken: CancellationToken = tokenSource.token; + this._quickInputService.pick(entries, + { placeHolder: nls.localize('TaskService.pickTask', 'Select a task to configure') }, cancellationToken). + then(async (entry) => { + if (cancellationToken.isCancellationRequested) { + // canceled when there's only one task + const task = (await entries)[0]; + if ((task).task) { + entry = task; + } + } + const task: Task | undefined | null = entry && 'task' in entry ? entry.task : undefined; + if ((task === undefined) || (task === null)) { + return; + } + if (task === selectedTask && CustomTask.is(task)) { + this.openConfig(task); + } + if (!InMemoryTask.is(task)) { + this.customize(task, { group: { kind: 'build', isDefault: true } }, true).then(() => { + if (selectedTask && (task !== selectedTask) && !InMemoryTask.is(selectedTask)) { + this.customize(selectedTask, { group: 'build' }, false); + } + }); + } + }); + this._quickInputService.pick(entries, { + placeHolder: nls.localize('TaskService.pickDefaultBuildTask', 'Select the task to be used as the default build task') + }). then((entry) => { - const task: Task | undefined | null = entry ? entry.task : undefined; + const task: Task | undefined | null = entry && 'task' in entry ? entry.task : undefined; if ((task === undefined) || (task === null)) { return; } From 38363843530badb12230e3d9c94f3e8e72733ca9 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 29 Jun 2022 14:11:11 -0400 Subject: [PATCH 074/150] fix find issue (#153732) fix #153728 --- package.json | 10 +++++----- remote/package.json | 10 +++++----- remote/web/package.json | 6 +++--- remote/web/yarn.lock | 24 ++++++++++++------------ remote/yarn.lock | 40 ++++++++++++++++++++-------------------- yarn.lock | 40 ++++++++++++++++++++-------------------- 6 files changed, 65 insertions(+), 65 deletions(-) diff --git a/package.json b/package.json index 5c101ca7aaf..607e5058946 100644 --- a/package.json +++ b/package.json @@ -87,12 +87,12 @@ "vscode-proxy-agent": "^0.12.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "7.0.1", - "xterm": "4.19.0-beta.67", - "xterm-addon-search": "0.9.0-beta.41", - "xterm-addon-serialize": "0.7.0-beta.15", + "xterm": "4.19.0", + "xterm-addon-search": "0.9.0", + "xterm-addon-serialize": "0.7.0", "xterm-addon-unicode11": "0.4.0-beta.3", - "xterm-addon-webgl": "0.12.0-beta.43", - "xterm-headless": "4.19.0-beta.67", + "xterm-addon-webgl": "0.12.0", + "xterm-headless": "4.19.0", "yauzl": "^2.9.2", "yazl": "^2.4.3" }, diff --git a/remote/package.json b/remote/package.json index 0f9cc3386d1..d9300a7cb68 100644 --- a/remote/package.json +++ b/remote/package.json @@ -26,12 +26,12 @@ "vscode-proxy-agent": "^0.12.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "7.0.1", - "xterm": "4.19.0-beta.67", - "xterm-addon-search": "0.9.0-beta.41", - "xterm-addon-serialize": "0.7.0-beta.15", + "xterm": "4.19.0", + "xterm-addon-search": "0.9.0", + "xterm-addon-serialize": "0.7.0", "xterm-addon-unicode11": "0.4.0-beta.3", - "xterm-addon-webgl": "0.12.0-beta.43", - "xterm-headless": "4.19.0-beta.67", + "xterm-addon-webgl": "0.12.0", + "xterm-headless": "4.19.0", "yauzl": "^2.9.2", "yazl": "^2.4.3" }, diff --git a/remote/web/package.json b/remote/web/package.json index cb364eb1e14..d73ebade4f4 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -12,9 +12,9 @@ "tas-client-umd": "0.1.6", "vscode-oniguruma": "1.6.1", "vscode-textmate": "7.0.1", - "xterm": "4.19.0-beta.67", - "xterm-addon-search": "0.9.0-beta.41", + "xterm": "4.19.0", + "xterm-addon-search": "0.9.0", "xterm-addon-unicode11": "0.4.0-beta.3", - "xterm-addon-webgl": "0.12.0-beta.43" + "xterm-addon-webgl": "0.12.0" } } diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index 7e38ab06f98..3f341e728fa 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -131,22 +131,22 @@ vscode-textmate@7.0.1: resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-7.0.1.tgz#8118a32b02735dccd14f893b495fa5389ad7de79" integrity sha512-zQ5U/nuXAAMsh691FtV0wPz89nSkHbs+IQV8FDk+wew9BlSDhf4UmWGlWJfTR2Ti6xZv87Tj5fENzKf6Qk7aLw== -xterm-addon-search@0.9.0-beta.41: - version "0.9.0-beta.41" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.9.0-beta.41.tgz#0992da36fe01ff6d71449265a9dabeef7f9d0a3f" - integrity sha512-b1vuWR5JZ8QIiObbKkwSzpzf4x0B9hdzGCHJG+PXWT/xbxk65DOe/X9rgrRyOnCWe5ylQGG4DIHTiREigTp0lg== +xterm-addon-search@0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.9.0.tgz#95278ebb818cfcf882209ae75be96e0bea5d52a5" + integrity sha512-aoolI8YuHvdGw+Qjg8g2M4kst0v86GtB7WeBm4F0jNXA005/6QbWWy9eCsvnIDLJOFI5JSSrZnD6CaOkvBQYPA== xterm-addon-unicode11@0.4.0-beta.3: version "0.4.0-beta.3" resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.4.0-beta.3.tgz#f350184155fafd5ad0d6fbf31d13e6ca7dea1efa" integrity sha512-FryZAVwbUjKTmwXnm1trch/2XO60F5JsDvOkZhzobV1hm10sFLVuZpFyHXiUx7TFeeFsvNP+S77LAtWoeT5z+Q== -xterm-addon-webgl@0.12.0-beta.43: - version "0.12.0-beta.43" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.43.tgz#916ed4e390371403aab0d277097cd8a61be436e8" - integrity sha512-hGXfwT6TOmp0tBDiS/iF8s0SLHLd3shJ5zQyS4HNtq99B5cEhHhwaUAZAfjLt5rnUt0kvqR9YWtGsDux5dWVLA== +xterm-addon-webgl@0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0.tgz#2fba8d31890a122adafa1c2fb945482e2ae12973" + integrity sha512-3P5ihdjPnxH6Wrvqjki9UD+duoVrp1fvnO/pSpXP2F1L2GwY6TDNExgj8Yg141vMCNgQbcVqmsTLYEYZxjY92A== -xterm@4.19.0-beta.67: - version "4.19.0-beta.67" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0-beta.67.tgz#9a7d79be64469c91bb693b9f5a50b5a22e921cd3" - integrity sha512-4bYTnT6g91N5QId1qPaiSvkpsxMQYpuPdPJItvBZFOcQLTuFSX85x5NeFuDvhNqi33eMFPimyGhzH/JvAV1v/Q== +xterm@4.19.0: + version "4.19.0" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0.tgz#c0f9d09cd61de1d658f43ca75f992197add9ef6d" + integrity sha512-c3Cp4eOVsYY5Q839dR5IejghRPpxciGmLWWaP9g+ppfMeBChMeLa1DCA+pmX/jyDZ+zxFOmlJL/82qVdayVoGQ== diff --git a/remote/yarn.lock b/remote/yarn.lock index 793a5a42ea6..8efe31b7992 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -932,35 +932,35 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -xterm-addon-search@0.9.0-beta.41: - version "0.9.0-beta.41" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.9.0-beta.41.tgz#0992da36fe01ff6d71449265a9dabeef7f9d0a3f" - integrity sha512-b1vuWR5JZ8QIiObbKkwSzpzf4x0B9hdzGCHJG+PXWT/xbxk65DOe/X9rgrRyOnCWe5ylQGG4DIHTiREigTp0lg== +xterm-addon-search@0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.9.0.tgz#95278ebb818cfcf882209ae75be96e0bea5d52a5" + integrity sha512-aoolI8YuHvdGw+Qjg8g2M4kst0v86GtB7WeBm4F0jNXA005/6QbWWy9eCsvnIDLJOFI5JSSrZnD6CaOkvBQYPA== -xterm-addon-serialize@0.7.0-beta.15: - version "0.7.0-beta.15" - resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.7.0-beta.15.tgz#0f7d5f9b423802ac67c2a891d74de530a4fe6a65" - integrity sha512-gO/dxqGgOAuj7DN2ETTeCHyalkb655XogZh5408CmH5D6mjI1lxqVLGUdiFeBVU/OHfWZT2PZ95k06VXqLU5kA== +xterm-addon-serialize@0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.7.0.tgz#cc7ef78972c8425b81dd6ae0a76824ce033d1e5f" + integrity sha512-ZfZ4Zj4uTEBFnUA0exipDGZ14jfiWLCov7gIt2OwIjQEz2ey8ic5kL/cxYz5antNz8/hTSA2qZcyA6VyyQASOQ== xterm-addon-unicode11@0.4.0-beta.3: version "0.4.0-beta.3" resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.4.0-beta.3.tgz#f350184155fafd5ad0d6fbf31d13e6ca7dea1efa" integrity sha512-FryZAVwbUjKTmwXnm1trch/2XO60F5JsDvOkZhzobV1hm10sFLVuZpFyHXiUx7TFeeFsvNP+S77LAtWoeT5z+Q== -xterm-addon-webgl@0.12.0-beta.43: - version "0.12.0-beta.43" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.43.tgz#916ed4e390371403aab0d277097cd8a61be436e8" - integrity sha512-hGXfwT6TOmp0tBDiS/iF8s0SLHLd3shJ5zQyS4HNtq99B5cEhHhwaUAZAfjLt5rnUt0kvqR9YWtGsDux5dWVLA== +xterm-addon-webgl@0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0.tgz#2fba8d31890a122adafa1c2fb945482e2ae12973" + integrity sha512-3P5ihdjPnxH6Wrvqjki9UD+duoVrp1fvnO/pSpXP2F1L2GwY6TDNExgj8Yg141vMCNgQbcVqmsTLYEYZxjY92A== -xterm-headless@4.19.0-beta.67: - version "4.19.0-beta.67" - resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.19.0-beta.67.tgz#fe44a243974bf307c9a0694114ab45289734c7f9" - integrity sha512-rpxUsE/te2LN4B/erI108uaAhuLTUsnRl9zIZgcqMqZGFRqDUTtyVM05jIA/8A32g06wFd3WioTkeKc6ow+sLg== +xterm-headless@4.19.0: + version "4.19.0" + resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.19.0.tgz#965eb293fe6258adff5888f24e2a0b778b765e17" + integrity sha512-rYP8I1AGwaztpCoWe9mwxNqmfz7zZCjbzw61QChFqPeiDERjW9CDnqyGhSElvicHAlf47dvA+p4qOuKltxWEkg== -xterm@4.19.0-beta.67: - version "4.19.0-beta.67" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0-beta.67.tgz#9a7d79be64469c91bb693b9f5a50b5a22e921cd3" - integrity sha512-4bYTnT6g91N5QId1qPaiSvkpsxMQYpuPdPJItvBZFOcQLTuFSX85x5NeFuDvhNqi33eMFPimyGhzH/JvAV1v/Q== +xterm@4.19.0: + version "4.19.0" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0.tgz#c0f9d09cd61de1d658f43ca75f992197add9ef6d" + integrity sha512-c3Cp4eOVsYY5Q839dR5IejghRPpxciGmLWWaP9g+ppfMeBChMeLa1DCA+pmX/jyDZ+zxFOmlJL/82qVdayVoGQ== yallist@^4.0.0: version "4.0.0" diff --git a/yarn.lock b/yarn.lock index 2fac594f909..714f0424253 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12256,35 +12256,35 @@ xtend@~2.1.1: dependencies: object-keys "~0.4.0" -xterm-addon-search@0.9.0-beta.41: - version "0.9.0-beta.41" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.9.0-beta.41.tgz#0992da36fe01ff6d71449265a9dabeef7f9d0a3f" - integrity sha512-b1vuWR5JZ8QIiObbKkwSzpzf4x0B9hdzGCHJG+PXWT/xbxk65DOe/X9rgrRyOnCWe5ylQGG4DIHTiREigTp0lg== +xterm-addon-search@0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.9.0.tgz#95278ebb818cfcf882209ae75be96e0bea5d52a5" + integrity sha512-aoolI8YuHvdGw+Qjg8g2M4kst0v86GtB7WeBm4F0jNXA005/6QbWWy9eCsvnIDLJOFI5JSSrZnD6CaOkvBQYPA== -xterm-addon-serialize@0.7.0-beta.15: - version "0.7.0-beta.15" - resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.7.0-beta.15.tgz#0f7d5f9b423802ac67c2a891d74de530a4fe6a65" - integrity sha512-gO/dxqGgOAuj7DN2ETTeCHyalkb655XogZh5408CmH5D6mjI1lxqVLGUdiFeBVU/OHfWZT2PZ95k06VXqLU5kA== +xterm-addon-serialize@0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.7.0.tgz#cc7ef78972c8425b81dd6ae0a76824ce033d1e5f" + integrity sha512-ZfZ4Zj4uTEBFnUA0exipDGZ14jfiWLCov7gIt2OwIjQEz2ey8ic5kL/cxYz5antNz8/hTSA2qZcyA6VyyQASOQ== xterm-addon-unicode11@0.4.0-beta.3: version "0.4.0-beta.3" resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.4.0-beta.3.tgz#f350184155fafd5ad0d6fbf31d13e6ca7dea1efa" integrity sha512-FryZAVwbUjKTmwXnm1trch/2XO60F5JsDvOkZhzobV1hm10sFLVuZpFyHXiUx7TFeeFsvNP+S77LAtWoeT5z+Q== -xterm-addon-webgl@0.12.0-beta.43: - version "0.12.0-beta.43" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.43.tgz#916ed4e390371403aab0d277097cd8a61be436e8" - integrity sha512-hGXfwT6TOmp0tBDiS/iF8s0SLHLd3shJ5zQyS4HNtq99B5cEhHhwaUAZAfjLt5rnUt0kvqR9YWtGsDux5dWVLA== +xterm-addon-webgl@0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0.tgz#2fba8d31890a122adafa1c2fb945482e2ae12973" + integrity sha512-3P5ihdjPnxH6Wrvqjki9UD+duoVrp1fvnO/pSpXP2F1L2GwY6TDNExgj8Yg141vMCNgQbcVqmsTLYEYZxjY92A== -xterm-headless@4.19.0-beta.67: - version "4.19.0-beta.67" - resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.19.0-beta.67.tgz#fe44a243974bf307c9a0694114ab45289734c7f9" - integrity sha512-rpxUsE/te2LN4B/erI108uaAhuLTUsnRl9zIZgcqMqZGFRqDUTtyVM05jIA/8A32g06wFd3WioTkeKc6ow+sLg== +xterm-headless@4.19.0: + version "4.19.0" + resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.19.0.tgz#965eb293fe6258adff5888f24e2a0b778b765e17" + integrity sha512-rYP8I1AGwaztpCoWe9mwxNqmfz7zZCjbzw61QChFqPeiDERjW9CDnqyGhSElvicHAlf47dvA+p4qOuKltxWEkg== -xterm@4.19.0-beta.67: - version "4.19.0-beta.67" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0-beta.67.tgz#9a7d79be64469c91bb693b9f5a50b5a22e921cd3" - integrity sha512-4bYTnT6g91N5QId1qPaiSvkpsxMQYpuPdPJItvBZFOcQLTuFSX85x5NeFuDvhNqi33eMFPimyGhzH/JvAV1v/Q== +xterm@4.19.0: + version "4.19.0" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0.tgz#c0f9d09cd61de1d658f43ca75f992197add9ef6d" + integrity sha512-c3Cp4eOVsYY5Q839dR5IejghRPpxciGmLWWaP9g+ppfMeBChMeLa1DCA+pmX/jyDZ+zxFOmlJL/82qVdayVoGQ== y18n@^3.2.1: version "3.2.2" From 3d9dd014337315d64dbb9f51254a63d5d8f71ac7 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Wed, 29 Jun 2022 11:15:19 -0700 Subject: [PATCH 075/150] Use English as the default instead of navigator.language (#153733) --- src/vs/base/common/platform.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/base/common/platform.ts b/src/vs/base/common/platform.ts index 94346ca4f68..5fd52a6fa76 100644 --- a/src/vs/base/common/platform.ts +++ b/src/vs/base/common/platform.ts @@ -68,7 +68,6 @@ const isElectronRenderer = isElectronProcess && nodeProcess?.type === 'renderer' interface INavigator { userAgent: string; - language: string; maxTouchPoints?: number; } declare const navigator: INavigator; @@ -90,7 +89,7 @@ if (typeof navigator === 'object' && !isElectronRenderer) { nls.localize({ key: 'ensureLoaderPluginIsLoaded', comment: ['{Locked}'] }, '_') ); - _locale = configuredLocale || navigator.language; + _locale = configuredLocale || LANGUAGE_DEFAULT; _language = _locale; } From 50e1886048d041a5281ee1affd04f1131f4a776b Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 29 Jun 2022 11:22:29 -0700 Subject: [PATCH 076/150] Fix slow integration test (#153735) Fixes #152886 --- .../src/singlefolder-tests/interactiveWindow.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts index 708ed02f709..0f85b437235 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts @@ -33,9 +33,9 @@ async function addCell(code: string, notebook: vscode.NotebookDocument) { return notebook.cellAt(notebook.cellCount - 1); } -async function addCellAndRun(code: string, notebook: vscode.NotebookDocument) { +async function addCellAndRun(code: string, notebook: vscode.NotebookDocument, i: number) { const cell = await addCell(code, notebook); - await vscode.commands.executeCommand('notebook.execute'); + await vscode.commands.executeCommand('notebook.cell.execute', { start: i, end: i + 1 }); assert.strictEqual(cell.outputs.length, 1, 'execute failed'); return cell; } @@ -78,7 +78,7 @@ async function addCellAndRun(code: string, notebook: vscode.NotebookDocument) { // Run and add a bunch of cells for (let i = 0; i < 10; i++) { - await addCellAndRun(`print ${i}`, notebookEditor.notebook); + await addCellAndRun(`print ${i}`, notebookEditor.notebook, i); } // Verify visible range has the last cell From 229c927bd7d740ca3e747a15a018256186916896 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 29 Jun 2022 11:31:00 -0700 Subject: [PATCH 077/150] Only inherit parent task icon if it's not set Fixes #153490 Co-authored-by: Megan Rogge --- src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index 5fef7f31d8e..ad1971db84f 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -512,7 +512,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { for (const dependency of task.configurationProperties.dependsOn) { const dependencyTask = await resolver.resolve(dependency.uri, dependency.task!); if (dependencyTask) { - dependencyTask.configurationProperties.icon = task.configurationProperties.icon; + dependencyTask.configurationProperties.icon = dependencyTask.configurationProperties.icon || task.configurationProperties.icon; const key = dependencyTask.getMapKey(); let promise = this._activeTasks[key] ? this._getDependencyPromise(this._activeTasks[key]) : undefined; if (!promise) { From 8a540edffd5e7401973fe9d790cae9eafc5bfdc2 Mon Sep 17 00:00:00 2001 From: David Dossett Date: Wed, 29 Jun 2022 11:31:27 -0700 Subject: [PATCH 078/150] Show `bell-slash-dot` icon when do not disturb is enabled (Fix #153483) (#153729) Show bell-slash-dot icon when do not disturb is enabled --- .../browser/ui/codicons/codicon/codicon.ttf | Bin 71728 -> 71980 bytes src/vs/base/common/codicons.ts | 1 + .../notifications/notificationsStatus.ts | 2 +- 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index 20ffef74fa80b13310f5e764c8127b0bc6d77e6b..1399909071888b7ce9b0e3873ec7c4248f2d9057 100644 GIT binary patch delta 7024 zcmYk>d0baT_XqIL0R&`qK|nwSR8&MlKtS9PaYMr$kyI2A5tUrX%>0^}nOUNsSy}mz zS(%w>rJ0#+wi#KOS(*8mnaz-8d*+kB&vg3ZXRr4?_kQ_uxik0PnKP$7dwh27^{I{b zjO+o(IsnsW&Mm2atRT`0@LvQ(*37E9xqQ!rOMe5M(t*=mE6Per3-8-Dj^8~?85R7% zYl_$V+&@H}D&{U)a`EYF{Jt~+zH6%H&n!9k-fa&9u{`$9n_IG^+Us?>jX#g%_haXk z%q{yky6C(Q1jYRD*XsET7d_VJ{&wR^^}IX_Oi$8 zvg|u9hrRZ#_4(7=)A-MK;Qcnd|Jh4o3;=u@;p4rPzugKg_=%TUW7e6s%saNZ9o^)A zS*~3J%yhHXtT*qNV>Zwh=x6+m|Nl?;`!i6ElQ<~_a?(4GKlH|F=^=^OA<;6=BLOl3 zzshI~llx?V43#|GiF66WGIq>&7$8-+5#h248?aouh&Qg_FY(|7Y{gEwStjE)sg$u& zBGY7?%#hhqDmP%46iPX~;DcstOdx^~j1YvfDXkEN7_>usjK>5_gl7_pFa>dlM@Mu* zXLLm(x+4iakc{h)f}Ti48ZwcEKIn^n=#POIgu%$g5DdjAjK=jSz%-O#CfiwwD$K<^ z%ttjAU?CP^F=}uVmf&VA#VxoM9<0P2SdF`|7I$Nv2lwD!tVb<2q8^*D8TVle?#F|8 z2oK{CerX&2g9bc~C-4-W#lvGKRbm=YG(ntDAKj|+yGFWnDhzyhAGE(wolw8mIJzgfr zM42Q-GDV7Is!W%eQYN#cT5go3a*HgJTRpN|R>*C#Qf`-3vPRa*-Lg*BORa2>I;oc} za=$zv56V{ACjY@#e0k2{YZPKKD$pCVQ7jdhg*hn0I3%D8e!}fogMD%rALa<$fX~H8 zGGvZ~;7J)I1I1s)NT{^KSlNgi{0z@3?1m9Ydvudv@k1|UAW!DXJ#weqA*-=ndPyd( zp;R7{N918yD2rsV)W}V;L>8bW@})WIWHX=gm+=nX##*wLkUMKGgI;XgDF+m!C0mwnz3BT zJjPi{I3}11#TOQ4w({Ut?o=wi#V~UehA~ztxsP$Kk^zkF#3Vx*=PSPVFx8490L+aF z96rqg#Zdufq2l-evxqMyH~);GW-&h?4jM2u3hs~Hq&SekEK#_zt~w;bvx!HyC=M?$ z?v#Y(jJGQ3!sw2PgAa^HaTtPGq3{>uZHhw^%u2;U3g&jj;R(Ex<#o@6uJ=Uo=uyRi6lS~PkP7pd;@}G7o+;um3-h?*fD6Ot)#1(NT@7)BhI!ic88;lPVRkDH+c3{44&X3*6o+z{XB7u^n7xX_ zJIp@Cfga{L#UUT&dBwpW#@#&PGyt<-vBx|x2Y5i78(>~koF!lmD$W-$FDcF(FozT$ zRhX9*XA_viit`H0D~dA=%n`-82If`8SqJ7dhmRKqa_4o$sR-r`#YqX~O~vU6=BVOC z1@o5Tlm+vS;^YN$OmP~6d0%ltgK1Qp+F(9VoaFfUALoWR-@$ySIP<}Lq&Nq{e5^Pd z!kkc?7hyh8oFQROD$bQKpDNCpFsBsfPngdXXHuBYm4}lm%omE&E6i!di5BKd#VHr& zjN;@A^OfQ>40Bd-LWcRT;?xXtPH~ck`C4(hhB>cz=V88a_x~HVekBhsD9+|E-zv`Q zFc%ePc$n`Lr*RmgIP1e$g>{TgiirTs_lhY2%q7L-0OkjUT6YycDrO8Ymlbmdn4c80 z2$-K0^9h(=6f+B$Ulnr}-r5XGztHdHacf^G2+_CH@Y+fp&!;gJK2<8>^TL!p13Pg|P98`5|ma#Y_>llVZ*Y+gZWG`)d;vczz!;R9r^y-K0F|C-P2cz>z;l} zT<`Q(;(BC&64xUGmAGCQq{Q_?juO}M!Ae}qbCs~sc8J6KFRtZzN?gl_Dse3zro^>; zxDwZr5lUQ3Mk;X)$yeeUGD?YS$Y>?5Aq7g@MS8|?L*lM-tP*#XD&O3AH^ zbCoP-oTp?3<9y}T{MP*ql9jyw*8LHZ+Zh)qS;e?e$r{E*O4c$iR&qCEjdDc`dy|s& zj7yZ%GTy9Y1EXsMNgbnWgohic=ZA0-U(EyJDiHQg#q}VpYc+9Y2z!^}8WGm@0CBYl z>w18=ZiIC`K(2aX?@?S!!rrU6s)ThtLtJ0Nx}K?Zsg8RPT-Oj6ov<4fm!Gh%n}`ch zSl3I$B`NGC#lUDOM+7k16CczOPuZfNfN$WBfp|iUE6Ed05bZ{ZO&A0sE0+kpuQ)#c~Ji3B|$( z>?evP5ZIH7#Sqv}70V*9rxXh$u%9WGN?_eXPArn)_OxOp1@=pY>5OL- zt1GZyDb`tF&nir<`>}n5$30$OE4at&ykZ>&_8Y~D4D1DkSjKM^t2D6xLTm1Sr&zgx zb&oEwh68IATyvTf%Q~>%yZ4A2R(N19Db{*me^9LY!2YOM|AD=%SP6ptNwFpb`?F$o z2=*7nIuY!ziWMW+-xO;{u)ixDr&zE;lfOb+;4;tcAd=j_dY=8F zgt4|*Rd+VFqo>yAY_nwkSKMrOvv++f{o4CY^}Fcr>p#!`Z1XJv`2iaP4h9?x3=2#M z%nBS4SR7arxF)bZaBtw5prD}Ype;d-LFa-4f|msE4E`e|H6%ZzA>?XkZfHeledxg! zkuAoysA+M&Wlqa=E%&tiyyb;fg{^kA4sTu4`czm*SZdgaup&=bWmrwv+OREQhuUPf z8Q*4Jn?vDA;f3Lq;oHM6MYNB|ikKd;EMjBC8xa>G10wrJ7DOIzi?#`E7q#8k_CVWH zZLdZ}MrB7;MQw>X7BswE{Ui8tJVKGZ%>SK}#=S;%3CHi1TcXI}q<1pBujpCT-HW=f?cS8sHECkf)}*UFl6s8jv8BhMDB4=>A&>~>6O^)NUu}9u4E)-)Mo6?L}pTEQRcSHqnQ^n|ISLxD#%)q zbvElt?}FY-d%w{8Qg(9ojO_aC*RrqniRv@8&+5KGee?Pr=@-!NQvZbhh5ZlrKQ|y{ zz{UY)V9LN%1FsG$7}PN6z@Upc$vNdY%X4<*c#aNE7+f^?d~Qze_S}<05{8T&a$v~C zypX)|yp?&iLq`nVGxWr;(ZecAaca1zg@|XOu{H*-? z{KiosqtZuJjA|H-(W#^7jlOby>Gfv{5(>5!oEQ@_ree(Qu~B2Q#!eiUJFaHjx^a8Q zT^%1Ye)RZ_6XHD+awjaGaCpMGi6IkHCYDdUIw@sR@uY^rqQX^^eI^f_ymRtLML|U? ziw;lmnvy$Z#gwDPt%@s)?=3!cL-h@BPkXy$dCAU_#uhcq_ie~**kx;RuVrRvL*?ng(pZ)IaXj7S6Sz6gp z**K@`oV==ls-<&d=GMw`D> zrA5TWCC9}{qi+;L CKd9~i delta 6772 zcmY+|dwh>|{|E5bdz+aV#%7zDnVHkf*f6J=nVG{JXNEcLWacn5rzQC!Bq@h!l_c&Y zl_cD#q$Mq+k|afPm(>0FtdgX1cXPi!-oJl-Jf7G4``x~~zSs4+uIuxDf8M+I`Rv{6 zvp(9B(FKsT0P+e;^XEilf7u%Fn*)U9O`Umvapw85$v{jnaB)J(l>DNxPi`B<&t9RV z68^wzyw_>IK0+}irSr;vSa$ag_g4e>u9!KyFh61Cw4*@O8NfTeG{1a~*D+bf--q$@ zQM2+(r+n5l_o@$YmtQEq&zU`Uo^AW#0f>|UA=Mj>w5+!s!q3v&gBZ4E@$BX<(W|K5OEZPuC(%t>3v z4y*CIdzU}SGi%H`bJCo)b?pfElmGWm_;-0I!a1CiVRDYY^TtJqlXiGP!lhK|$ROO5 zp;;Ipm69SE(jTkPS%UBY-olSakr|kS5ZQ$Fcu-o47yiO+S%QD!8N4LrG8Ri^nv9e@ znIu_~FC|ha6Yz?Rktr2zlcih4s z_!FML@n7K`AwE(|>PiC%l!nqsf~B#9NT`HK6N!}O5+yApT3Sksw30T`R$`^SbdY%I zD4irh5~Z_rmmbnndP%bMmcEiE{Ulum$Uqq^nKFbIcDRg^Y#A*%GEQ=3yiAnIQXoYh zDVEtXNA8zJ@_;Os2gM^xWtl9OhvZ>-MApco^0=&%^|Dbaj65gXa2em= zU-%YdFcwqM9VHkqQ&EiRyqmJn3a#-gmg8aUlhwRqLooqginnx?=~557BvtxIZSjnd z`VxVWvH^W@0}tU<{2OZvnTKYsxcUYFc86b8~?x#jKE~+hNm$Rjj;!>iT@BpqB(v* zkUWO}-~vwK6h6U6_!yt!ET4(<_yVW#q0GXY(i9%}qBi_c2cP2%K0`S6<0sUh2l&W~ z@BY34UgAN3d%l9U=u;-!-4m$pJ)ik}O}NNdpd^m5Q1Q8gDN=ZWaf*^~#$qLCm-fMh0cs+ zin9UCT$RF!0cM`!6ah0|ak79}py0l2q2dGrQ?4+lVoCi_&nCWedm&CMFpCu)Wc)wU zny;5APC7hq#pwrTslsi>Wr|Z0%yPxa3FaZiX$oeA!b^-R0gu~=J0y3$#90jHVFkDI z)k@r0uTe6R@lhptjBAxlVqB*ri_x7BiMtUtD3=fnCvis#InS92z9CMPFm5ymuP{EL zWDKLbc*KeHSc7$(NMW8=oKj(SC{C_0FDOp4Fgq0|T$mRXr(T#{ijy#m8wcWa4D+($ z#0=w(fH+0N>{f_keASH^H=MU&_9)KaFs~`j<{BE7%>mP98}B*FozU#g9qj?-w=ln<_*P!0rRF}>VSDmF^Rw&QA{T=Z!0Din4^j* z2Id{bWCK&Bn08>^RZKuI#~j{%%tbKoDWozUSIkc^?<;01m}jaoz6xQ+jo9l|j1I(|AWdzI(#exFnH^tHd=B8qi0rR_J zxdC%avG9QTL$L&b`BSkNf%!|ZEP=UA9&T8n!2CzuJF~y}npm~K{8wQc;~m9H2Ij6} zO~dj;vATg3#X1MpOR?gC^;WEXV0{#;Ab4yoz9AMwu)d0=5o~S6A_>+{v0Q?!qgXh> z)>SN_U;`A3DcE`nRgCo&3oO_Milr88;5{7w_xPfrV*Lf%NU;)w4N|PhV1pH_GuXz8 zbsB7l!WG6)#o7%vOtFfCZK7Du!G3HM~NGqR3&ai z`YLfFlBUFsLO&&L6w;NrQRwgR`U^+EW+-u^FhGeLg@H=kC=62K&T6m{cUGB7+*u7# z;!bL)5_eL=l(>@`p~M}qXCyZy?ufILxFa5=#2s)-k#RCt1(v4xD5o zqdRa9H&VeD?#M}=WOPSP@)Y9|B~LTDgC*I_=+1)V8OCKwo?~<;L9&g}odj{k2fIRX z?FYM3aTUnte-+;l7lg3xOo>ZF*wu=QL|Av$#N{HaJ8R;?5!RhGaR~{#R$)BjV~Wd4 zST`EP1tzQ;4dPN0){Vw`@GfyT!Uo0lC#)M9;z|_OjSO*33cE>hbqf20;yM+!QgOu! z>qd{TkI{`D$?A%&O~6*NHteg4 zD{t66ifeG#*A(tD?p0iu^ErE+ZwPG}-T06MFz#1ew!-<4VdH-&Zo1(NoP0$vnmnl+0&5p~OANCzUK@Jf+y4fc;RhQ2~2eA(QbV z#pVU<#|j%6KT&LGz@AYa_BLQYRjxB>&nosiU_Vpre88Sl?18|3uGkHMJ+Igof&D_U zLjrq2u~!1?Zfs)L1okV%{t4_wu3xwTTPd)Y6q_orUn@*zysX%0f&E6Yx!))*k2W!La;X!+e5IwDK?5=Zz{HoV1HL^9>LyH zY$L(`q3|IWsrFC4A@-JF|59-G`)$R36RbN6f~O)as#iWQV~r^GpHSna*oi`ow_;BU zH9m^nDb&{Fq}SFvM-n%auJE7b75a}ZWh6V=(X$mazA->NmRR*|od?_u9RYcH?8 z&CkzoQJus(*ZkxBv;B+x_xM-&pY^}$f2(d_-RQdUb%)d~ue&qAKOixnBw%sCj(Uyi zrPte7?{vLu^^@yY*1z1qt3iB&><0T9)C2|wE(yHUuyw=14d*pn+VDoBj7EEd0)xte zb_Jadx*Bvd*vAtb7#tIv7@QM)F8JrhevNY)S2w;9;uDe*vO45&$k~wFp&_Ahp_4+F zhVBTx5Ng7vhiwk4YEr*RT$8LOWlbuZ>}zr^+&4TiJUe`G_^$9XP5qkYHT^asI3hlx zIO2FS-)7~_&P4`9PKvBFm`xxpR5vQ(gSK#CKWWWmlKuU30sZb=}mpIw>}3 zVAAHK>ZF_9dUng}wzS(*-HvxN-NU;t>b|MF=@HyxM32=yPW1S@XME2IJu7=2==o=_ zs9rmJT}jSO-qCw?@2cK6QpTn%PC3;luFr}-r&1$RD^pG1)U>=b zPgUB@eoOjYN>53jlU|ix(?6^K^8ULrkP(p)n{jo(-~qD+{5Y`Fz-@zk2W1UKAstwnUOg?^YP3*nP-Q@4=EY4Yskk#y@nJ+a8&D2Lq=7OIymab?6~YB*_S<|!$yxCy?FGl(Wi2dQ<-xr z=hm3yF*nCXj@>%;t8wY$W{%sL8<9IUcT4W&@!{k1#_yisGokH-vWfl^3nmxjhvlc| zm*n3l2q{P_$Sv4iaHnuf;rYUAg|~`)i$aPP6df$OH6?Y*swuaMql#x0-<-OBYE?<2 zlJt_LCFiGwO)HwVVp{0sX?Lb4PcNB%Z2FZMJ7xyYJXjiET2{Jef0vlie)~svi27W% YA6nogY2Jt4ti_J}p`U!EPb0zq0BCfGw*UYD diff --git a/src/vs/base/common/codicons.ts b/src/vs/base/common/codicons.ts index b452a203600..9e696833141 100644 --- a/src/vs/base/common/codicons.ts +++ b/src/vs/base/common/codicons.ts @@ -558,6 +558,7 @@ export class Codicon implements CSSIcon { public static readonly mapFilled = new Codicon('map-filled', { fontCharacter: '\\ec06' }); public static readonly circleSmall = new Codicon('circle-small', { fontCharacter: '\\ec07' }); public static readonly bellSlash = new Codicon('bell-slash', { fontCharacter: '\\ec08' }); + public static readonly bellSlashDot = new Codicon('bell-slash-dot', { fontCharacter: '\\f101' }); // derived icons, that could become separate icons diff --git a/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts b/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts index 9b15769a6c3..d3da50394ad 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts @@ -87,7 +87,7 @@ export class NotificationsStatus extends Disposable { statusProperties = { ...statusProperties, name: localize('status.doNotDisturb', "Do Not Disturb"), - text: '$(bell-slash)', + text: `${notificationsInProgress > 0 || this.newNotificationsCount > 0 ? '$(bell-slash-dot)' : '$(bell-slash)'}`, ariaLabel: localize('status.doNotDisturb', "Do Not Disturb"), tooltip: localize('status.doNotDisturbTooltip', "Do Not Disturb Mode is Enabled") }; From 6ca8db83e765d4afda4b616ea7a918dcc1097d38 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 29 Jun 2022 20:45:21 +0200 Subject: [PATCH 079/150] Git - Disable undo last commit command while commit is in progress (#153739) Disable undo last commit command while commit is in progress --- extensions/git/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 3e706e0ec6a..acd208f3ac1 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -327,7 +327,8 @@ { "command": "git.undoCommit", "title": "%command.undoCommit%", - "category": "Git" + "category": "Git", + "enablement": "!commitInProgress" }, { "command": "git.checkout", From d4f769464d9069230e7b5580ce542c01b3672bb3 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Wed, 29 Jun 2022 20:51:46 +0200 Subject: [PATCH 080/150] Always ask the editor to layout when the hover contents change (#153740) Fixes #151235: Always ask the editor to layout when the hover contents change --- src/vs/editor/contrib/hover/browser/contentHover.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/hover/browser/contentHover.ts b/src/vs/editor/contrib/hover/browser/contentHover.ts index 30204ef7f6a..93941e6172c 100644 --- a/src/vs/editor/contrib/hover/browser/contentHover.ts +++ b/src/vs/editor/contrib/hover/browser/contentHover.ts @@ -415,7 +415,6 @@ export class ContentHoverWidget extends Disposable implements IContentWidget { this._hover.contentsDomNode.style.paddingBottom = ''; this._updateFont(); - this._editor.layoutContentWidget(this); this.onContentsChanged(); // Simply force a synchronous render on the editor @@ -424,7 +423,6 @@ export class ContentHoverWidget extends Disposable implements IContentWidget { // See https://github.com/microsoft/vscode/issues/140339 // TODO: Doing a second layout of the hover after force rendering the editor - this._editor.layoutContentWidget(this); this.onContentsChanged(); if (visibleData.stoleFocus) { @@ -447,6 +445,7 @@ export class ContentHoverWidget extends Disposable implements IContentWidget { } public onContentsChanged(): void { + this._editor.layoutContentWidget(this); this._hover.onContentsChanged(); const scrollDimensions = this._hover.scrollbar.getScrollDimensions(); From ab301f9226f51d48af8e3527c48d9df240da6e83 Mon Sep 17 00:00:00 2001 From: David Dossett Date: Wed, 29 Jun 2022 12:06:41 -0700 Subject: [PATCH 081/150] Always hide notification center when toggling do not disturb mode (Fix #153557) (#153726) --- .../browser/parts/notifications/notificationsCenter.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts index 8a6cafd8971..a230bb759e2 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts @@ -68,9 +68,7 @@ export class NotificationsCenter extends Themable implements INotificationsCente } private onDidChangeDoNotDisturbMode(): void { - if (this.notificationService.doNotDisturbMode) { - this.hide(); // hide the notification center when do not disturb is enabled - } + this.hide(); // hide the notification center when do not disturb is toggled } get isVisible(): boolean { From 0d3e7e6d9ab9f57cbcba503129aa18ac890a4dad Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 29 Jun 2022 21:07:23 +0200 Subject: [PATCH 082/150] Fix #153533 (#153717) --- .../common/profileAwareExtensionManagementService.ts | 2 +- .../electron-sandbox/extensionManagementServerService.ts | 7 +++++++ .../userDataProfile/browser/userDataProfileManagement.ts | 5 +++++ .../userDataProfile/common/userDataProfileService.ts | 6 +++--- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/services/extensionManagement/common/profileAwareExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/profileAwareExtensionManagementService.ts index 781b4557058..b8a639d4e9e 100644 --- a/src/vs/workbench/services/extensionManagement/common/profileAwareExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/profileAwareExtensionManagementService.ts @@ -31,7 +31,7 @@ export class NativeProfileAwareExtensionManagementService extends ExtensionManag private readonly _onDidChangeProfileExtensions = this._register(new Emitter<{ readonly added: ILocalExtension[]; readonly removed: ILocalExtension[] }>()); readonly onDidChangeProfileExtensions = this._onDidChangeProfileExtensions.event; - constructor(channel: IChannel, private extensionsProfileResource: URI | undefined, + constructor(channel: IChannel, public extensionsProfileResource: URI | undefined, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, ) { super(channel); diff --git a/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementServerService.ts b/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementServerService.ts index ece4c6091b0..762987822ee 100644 --- a/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementServerService.ts +++ b/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementServerService.ts @@ -17,6 +17,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { NativeProfileAwareExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/profileAwareExtensionManagementService'; import { Disposable } from 'vs/base/common/lifecycle'; +import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; export class ExtensionManagementServerService extends Disposable implements IExtensionManagementServerService { @@ -30,12 +31,18 @@ export class ExtensionManagementServerService extends Disposable implements IExt @ISharedProcessService sharedProcessService: ISharedProcessService, @IRemoteAgentService remoteAgentService: IRemoteAgentService, @ILabelService labelService: ILabelService, + @IUserDataProfilesService userDataProfilesService: IUserDataProfilesService, @IUserDataProfileService userDataProfileService: IUserDataProfileService, @IInstantiationService instantiationService: IInstantiationService, ) { super(); const localExtensionManagementService = this._register(instantiationService.createInstance(NativeProfileAwareExtensionManagementService, sharedProcessService.getChannel('extensions'), userDataProfileService.currentProfile.extensionsResource)); this.localExtensionManagementServer = { extensionManagementService: localExtensionManagementService, id: 'local', label: localize('local', "Local") }; + this._register(userDataProfilesService.onDidChangeProfiles(e => { + if (userDataProfileService.currentProfile.isDefault) { + localExtensionManagementService.extensionsProfileResource = userDataProfilesService.defaultProfile.extensionsResource; + } + })); this._register(userDataProfileService.onDidChangeCurrentProfile(e => e.join(localExtensionManagementService.switchExtensionsProfile(e.profile.extensionsResource)))); const remoteAgentConnection = remoteAgentService.getConnection(); if (remoteAgentConnection) { diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts index 0f4c910c0f1..2488577017b 100644 --- a/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts @@ -55,6 +55,11 @@ export class UserDataProfileManagementService extends Disposable implements IUse private onDidChangeProfiles(e: DidChangeProfilesEvent): void { if (e.removed.some(profile => profile.id === this.userDataProfileService.currentProfile.id)) { this.enterProfile(this.userDataProfilesService.defaultProfile, false, localize('reload message when removed', "The current profile has been removed. Please reload to switch back to default profile")); + return; + } + if (this.userDataProfileService.currentProfile.isDefault) { + this.userDataProfileService.updateCurrentProfile(this.userDataProfilesService.defaultProfile, false); + return; } } diff --git a/src/vs/workbench/services/userDataProfile/common/userDataProfileService.ts b/src/vs/workbench/services/userDataProfile/common/userDataProfileService.ts index e29eefe0e24..96fa879239f 100644 --- a/src/vs/workbench/services/userDataProfile/common/userDataProfileService.ts +++ b/src/vs/workbench/services/userDataProfile/common/userDataProfileService.ts @@ -25,11 +25,11 @@ export class UserDataProfileService extends Disposable implements IUserDataProfi } async updateCurrentProfile(userDataProfile: IUserDataProfile, preserveData: boolean): Promise { - if (this._currentProfile.id === userDataProfile.id) { - return; - } const previous = this._currentProfile; this._currentProfile = userDataProfile; + if (this._currentProfile.id === previous.id) { + return; + } const joiners: Promise[] = []; this._onDidChangeCurrentProfile.fire({ preserveData, From 257bb1ae83dbb3201b1a41f008f2c1891532350e Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 29 Jun 2022 21:40:21 +0200 Subject: [PATCH 083/150] Update seti for the gitcommit language (#153711) --- extensions/theme-seti/cgmanifest.json | 2 +- extensions/theme-seti/icons/seti.woff | Bin 37192 -> 37192 bytes .../theme-seti/icons/vs-seti-icon-theme.json | 4 +++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/extensions/theme-seti/cgmanifest.json b/extensions/theme-seti/cgmanifest.json index 8bcfb2d2f40..983ec9c5157 100644 --- a/extensions/theme-seti/cgmanifest.json +++ b/extensions/theme-seti/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "seti-ui", "repositoryUrl": "https://github.com/jesseweed/seti-ui", - "commitHash": "6b83574de165123583d6d8d5b3b6c91f04b7153d" + "commitHash": "4dd6c27e1f5aed8068c2451dbaf0db3364545937" } }, "version": "0.1.0" diff --git a/extensions/theme-seti/icons/seti.woff b/extensions/theme-seti/icons/seti.woff index 70e9074c0fb5e511be11eac50abb6e2f95d1c4de..24cde7296648c637c0f140bb90bb1a1f2986ec09 100644 GIT binary patch delta 59 zcmV-B0L1^uq5{aG0+5XZwTCFNklBePzyAkw;`wd9GH^4$0E#f&d!!hJOfz3*?gvsF R3``&%08&8;`m>ITx&}ad8K?jN delta 59 zcmV-B0L1^uq5{aG0+5XZkH&nlklBePe{ZKF@%%Pl8Mv8W07V$?op)qGrkO7@_X8;o R1||>>03MYIjfV^ diff --git a/extensions/theme-seti/icons/vs-seti-icon-theme.json b/extensions/theme-seti/icons/vs-seti-icon-theme.json index 6f7f20bd8c3..0914afe46c1 100644 --- a/extensions/theme-seti/icons/vs-seti-icon-theme.json +++ b/extensions/theme-seti/icons/vs-seti-icon-theme.json @@ -1881,6 +1881,7 @@ "dockerfile": "_docker", "ignore": "_git", "fsharp": "_f-sharp", + "git-commit": "_git", "go": "_go2", "groovy": "_grails", "handlebars": "_mustache", @@ -2197,6 +2198,7 @@ "dockerfile": "_docker_light", "ignore": "_git_light", "fsharp": "_f-sharp_light", + "git-commit": "_git_light", "go": "_go2_light", "groovy": "_grails_light", "handlebars": "_mustache_light", @@ -2338,5 +2340,5 @@ "npm-debug.log": "_npm_ignored_light" } }, - "version": "https://github.com/jesseweed/seti-ui/commit/6b83574de165123583d6d8d5b3b6c91f04b7153d" + "version": "https://github.com/jesseweed/seti-ui/commit/4dd6c27e1f5aed8068c2451dbaf0db3364545937" } \ No newline at end of file From 290210f496ca2299a21f715e2a0630a4ec102b68 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 29 Jun 2022 13:30:08 -0700 Subject: [PATCH 084/150] Allow tree to specify `'files'` as drop type (#153751) For https://github.com/microsoft/vscode/issues/153404#issuecomment-1170400253 This special type indicates that the tree wants to recieve any type of file that is dropped. This matches how the dom drop apis work --- src/vs/workbench/browser/parts/views/treeView.ts | 2 +- src/vscode-dts/vscode.d.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index 64889868f10..cf1635f6f83 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -1499,7 +1499,7 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop { const outDataTransfer = new VSDataTransfer(); for (const [type, item] of originalDataTransfer.entries()) { - if (type === this.treeMimeType || dndController.dropMimeTypes.indexOf(type) >= 0) { + if (type === this.treeMimeType || dndController.dropMimeTypes.includes(type) || (item.asFile() && dndController.dropMimeTypes.includes(DataTransfers.FILES.toLowerCase()))) { outDataTransfer.append(type, item); if (type === this.treeMimeType) { try { diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index 7e0124da203..31027c91115 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -10166,6 +10166,8 @@ declare module 'vscode' { * This includes drops from within the same tree. * The mime type of a tree is recommended to be of the format `application/vnd.code.tree.`. * + * Use the special `files` mime type to support all types of dropped files {@link DataTransferFile files}, regardless of the file's actual mime type. + * * To learn the mime type of a dragged item: * 1. Set up your `DragAndDropController` * 2. Use the Developer: Set Log Level... command to set the level to "Debug" From 39a346c92b988b27aafc9fb243935db5b23b2517 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 29 Jun 2022 14:13:07 -0700 Subject: [PATCH 085/150] Fix notebook cell editor navigate context keys to not override suggest widget (#153736) Fixes #152770 --- .../browser/contrib/navigation/arrow.ts | 65 +++++++++++-------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts b/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts index e28f4f054bc..8c8a8fb1f59 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { localize } from 'vs/nls'; import { registerAction2 } from 'vs/platform/actions/common/actions'; @@ -14,10 +15,9 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; import { INotebookActionContext, INotebookCellActionContext, NotebookAction, NotebookCellAction, NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; -import { NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_TYPE, NOTEBOOK_CURSOR_NAVIGATION_MODE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; import { CellEditState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellKind, NOTEBOOK_EDITOR_CURSOR_BOUNDARY } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; +import { NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_TYPE, NOTEBOOK_CURSOR_NAVIGATION_MODE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; const NOTEBOOK_FOCUS_TOP = 'notebook.focusTop'; const NOTEBOOK_FOCUS_BOTTOM = 'notebook.focusBottom'; @@ -42,22 +42,27 @@ registerAction2(class FocusNextCellAction extends NotebookCellAction { when: ContextKeyExpr.and( NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.equals('config.notebook.navigation.allowNavigateToSurroundingCells', true), - ContextKeyExpr.or( - ContextKeyExpr.and( - ContextKeyExpr.has(InputFocusedContextKey), - EditorContextKeys.editorTextFocus, - NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('top'), - NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('none'), - ), - ContextKeyExpr.and( - NOTEBOOK_CELL_TYPE.isEqualTo('markup'), - NOTEBOOK_CELL_MARKDOWN_EDIT_MODE.isEqualTo(false), - NOTEBOOK_CURSOR_NAVIGATION_MODE - ) - ) + ContextKeyExpr.and( + ContextKeyExpr.has(InputFocusedContextKey), + EditorContextKeys.editorTextFocus, + NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('top'), + NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('none'), + ), ), primary: KeyCode.DownArrow, - weight: KeybindingWeight.WorkbenchContrib, + weight: NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT, // code cell keybinding, focus inside editor: lower weight to not override suggest widget + }, + { + when: ContextKeyExpr.and( + NOTEBOOK_EDITOR_FOCUSED, + ContextKeyExpr.equals('config.notebook.navigation.allowNavigateToSurroundingCells', true), + ContextKeyExpr.and( + NOTEBOOK_CELL_TYPE.isEqualTo('markup'), + NOTEBOOK_CELL_MARKDOWN_EDIT_MODE.isEqualTo(false), + NOTEBOOK_CURSOR_NAVIGATION_MODE) + ), + primary: KeyCode.DownArrow, + weight: KeybindingWeight.WorkbenchContrib, // markdown keybinding, focus on list: higher weight to override list.focusDown }, { when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_OUTPUT_FOCUSED), @@ -95,27 +100,35 @@ registerAction2(class FocusPreviousCellAction extends NotebookCellAction { super({ id: NOTEBOOK_FOCUS_PREVIOUS_EDITOR, title: localize('cursorMoveUp', 'Focus Previous Cell Editor'), - keybinding: { - when: ContextKeyExpr.and( - NOTEBOOK_EDITOR_FOCUSED, - ContextKeyExpr.equals('config.notebook.navigation.allowNavigateToSurroundingCells', true), - ContextKeyExpr.or( + keybinding: [ + { + when: ContextKeyExpr.and( + NOTEBOOK_EDITOR_FOCUSED, + ContextKeyExpr.equals('config.notebook.navigation.allowNavigateToSurroundingCells', true), ContextKeyExpr.and( ContextKeyExpr.has(InputFocusedContextKey), EditorContextKeys.editorTextFocus, NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('bottom'), NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('none'), ), + ), + primary: KeyCode.UpArrow, + weight: NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT, // code cell keybinding, focus inside editor: lower weight to not override suggest widget + }, + { + when: ContextKeyExpr.and( + NOTEBOOK_EDITOR_FOCUSED, + ContextKeyExpr.equals('config.notebook.navigation.allowNavigateToSurroundingCells', true), ContextKeyExpr.and( NOTEBOOK_CELL_TYPE.isEqualTo('markup'), NOTEBOOK_CELL_MARKDOWN_EDIT_MODE.isEqualTo(false), NOTEBOOK_CURSOR_NAVIGATION_MODE ) - ) - ), - primary: KeyCode.UpArrow, - weight: KeybindingWeight.WorkbenchContrib, - }, + ), + primary: KeyCode.UpArrow, + weight: KeybindingWeight.WorkbenchContrib, // markdown keybinding, focus on list: higher weight to override list.focusDown + } + ], }); } From b50cc59ebeb0815b6f38d3fce1564ac458acc13c Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 29 Jun 2022 16:25:01 -0700 Subject: [PATCH 086/150] Make tree view always write `text/uri-list` (#153758) Fixes #152836 This forces the treeview to always write `text/uri-list` when it can. Previouly we only wrote it when there was not already a `text/uri-list` on the data transfer I have no clue where the original `text/uri-list` is coming from and it only seems to get set on windows --- src/vs/editor/browser/dnd.ts | 4 ++-- src/vs/workbench/browser/parts/views/treeView.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/browser/dnd.ts b/src/vs/editor/browser/dnd.ts index 65d83ee29b8..de99d817fcd 100644 --- a/src/vs/editor/browser/dnd.ts +++ b/src/vs/editor/browser/dnd.ts @@ -41,8 +41,8 @@ const INTERNAL_DND_MIME_TYPES = Object.freeze([ DataTransfers.RESOURCES, ]); -export function addExternalEditorsDropData(dataTransfer: VSDataTransfer, dragEvent: DragEvent) { - if (dragEvent.dataTransfer && !dataTransfer.has(Mimes.uriList)) { +export function addExternalEditorsDropData(dataTransfer: VSDataTransfer, dragEvent: DragEvent, overwriteUriList = false) { + if (dragEvent.dataTransfer && (overwriteUriList || !dataTransfer.has(Mimes.uriList))) { const editorData = extractEditorsDropData(dragEvent) .filter(input => input.resource) .map(input => input.resource!.toString()); diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index cf1635f6f83..67efd200c19 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -1495,7 +1495,7 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop { } const originalDataTransfer = toVSDataTransfer(originalEvent.dataTransfer); - addExternalEditorsDropData(originalDataTransfer, originalEvent); + addExternalEditorsDropData(originalDataTransfer, originalEvent, true); const outDataTransfer = new VSDataTransfer(); for (const [type, item] of originalDataTransfer.entries()) { From f1abeeab7e51df997bbb0f1ff900321ad2f21ac0 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Wed, 29 Jun 2022 16:31:25 -0700 Subject: [PATCH 087/150] Fix #153526 (#153766) --- .../sessionSync/browser/sessionSync.contribution.ts | 3 ++- .../sessionSync/browser/sessionSyncWorkbenchService.ts | 9 ++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts b/src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts index 77932003da9..6aa03f41d3c 100644 --- a/src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts +++ b/src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts @@ -290,8 +290,9 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon } } - this.logService.info(`Edit Sessions: Deleting edit session with ref ${ref} after successfully applying it to current workspace.`); + this.logService.info(`Edit Sessions: Deleting edit session with ref ${ref} after successfully applying it to current workspace...`); await this.sessionSyncWorkbenchService.delete(ref); + this.logService.info(`Edit Sessions: Deleted edit session with ref ${ref}.`); } catch (ex) { this.logService.error('Edit Sessions: Failed to apply edit session, reason: ', (ex as Error).toString()); this.notificationService.error(localize('apply failed', "Failed to apply your edit session.")); diff --git a/src/vs/workbench/services/sessionSync/browser/sessionSyncWorkbenchService.ts b/src/vs/workbench/services/sessionSync/browser/sessionSyncWorkbenchService.ts index 54c386724ba..aea04417075 100644 --- a/src/vs/workbench/services/sessionSync/browser/sessionSyncWorkbenchService.ts +++ b/src/vs/workbench/services/sessionSync/browser/sessionSyncWorkbenchService.ts @@ -120,6 +120,9 @@ export class SessionSyncWorkbenchService extends Disposable implements ISessionS } private async initialize() { + if (this.initialized) { + return; + } this.initialized = await this.doInitialize(); this.signedInContext.set(this.initialized); } @@ -152,9 +155,12 @@ export class SessionSyncWorkbenchService extends Disposable implements ISessionS } // If the user signed in previously and the session is still available, reuse that without prompting the user again - if (this.existingSessionId) { + const existingSessionId = this.existingSessionId; + if (existingSessionId) { + this.logService.trace(`Edit Sessions: Searching for existing authentication session with ID ${existingSessionId}`); const existing = await this.getExistingSession(); if (existing !== undefined) { + this.logService.trace(`Edit Sessions: Found existing authentication session with ID ${existingSessionId}`); this.#authenticationInfo = { sessionId: existing.session.id, token: existing.session.accessToken, providerId: existing.session.providerId }; this.storeClient.setAuthToken(this.#authenticationInfo.token, this.#authenticationInfo.providerId); return true; @@ -167,6 +173,7 @@ export class SessionSyncWorkbenchService extends Disposable implements ISessionS this.#authenticationInfo = { sessionId: session.id, token: session.accessToken, providerId: session.providerId }; this.storeClient.setAuthToken(this.#authenticationInfo.token, this.#authenticationInfo.providerId); this.existingSessionId = session.id; + this.logService.trace(`Edit Sessions: Saving authentication session preference for ID ${session.id}.`); return true; } From 944c6ca1045f6fad00073e96ef2da42fe30d48dd Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Thu, 30 Jun 2022 09:48:18 +0200 Subject: [PATCH 088/150] Support removing and adding the same extension but at different versions (#153761) Fixes #153646: Support removing and adding the same extension but at different versions --- .../common/abstractExtensionService.ts | 56 ++++++++++--------- .../common/extensionDescriptionRegistry.ts | 39 +++++++++---- .../extensionDescriptionRegistry.test.ts | 40 +++++++++++++ 3 files changed, 99 insertions(+), 36 deletions(-) create mode 100644 src/vs/workbench/services/extensions/test/common/extensionDescriptionRegistry.test.ts diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts index 3bd6b000b7b..5314122757d 100644 --- a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts +++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts @@ -546,23 +546,6 @@ export abstract class AbstractExtensionService extends Disposable implements IEx } private async _deltaExtensions(_toAdd: IExtension[], _toRemove: string[] | IExtension[]): Promise { - const toAdd: IExtensionDescription[] = []; - for (let i = 0, len = _toAdd.length; i < len; i++) { - const extension = _toAdd[i]; - - const extensionDescription = await this._scanSingleExtension(extension); - if (!extensionDescription) { - // could not scan extension... - continue; - } - - if (!this.canAddExtension(extensionDescription)) { - continue; - } - - toAdd.push(extensionDescription); - } - let toRemove: IExtensionDescription[] = []; for (let i = 0, len = _toRemove.length; i < len; i++) { const extensionOrId = _toRemove[i]; @@ -587,6 +570,23 @@ export abstract class AbstractExtensionService extends Disposable implements IEx toRemove.push(extensionDescription); } + const toAdd: IExtensionDescription[] = []; + for (let i = 0, len = _toAdd.length; i < len; i++) { + const extension = _toAdd[i]; + + const extensionDescription = await this._scanSingleExtension(extension); + if (!extensionDescription) { + // could not scan extension... + continue; + } + + if (!this._canAddExtension(extensionDescription, toRemove)) { + continue; + } + + toAdd.push(extensionDescription); + } + if (toAdd.length === 0 && toRemove.length === 0) { return; } @@ -643,15 +643,19 @@ export abstract class AbstractExtensionService extends Disposable implements IEx } public canAddExtension(extension: IExtensionDescription): boolean { - const existing = this._registry.getExtensionDescription(extension.identifier); - if (existing) { - // this extension is already running (most likely at a different version) - return false; - } + return this._canAddExtension(extension, []); + } - // Check if extension is renamed - if (extension.uuid && this._registry.getAllExtensionDescriptions().some(e => e.uuid === extension.uuid)) { - return false; + private _canAddExtension(extension: IExtensionDescription, extensionsBeingRemoved: IExtensionDescription[]): boolean { + // (Also check for renamed extensions) + const existing = this._registry.getExtensionDescriptionByIdOrUUID(extension.identifier, extension.id); + if (existing) { + // This extension is already known (most likely at a different version) + // so it cannot be added again unless it is removed first + const isBeingRemoved = extensionsBeingRemoved.some((extensionDescription) => ExtensionIdentifier.equals(extension.identifier, extensionDescription.identifier)); + if (!isBeingRemoved) { + return false; + } } const extensionKind = this._getExtensionKind(extension); @@ -667,7 +671,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx public canRemoveExtension(extension: IExtensionDescription): boolean { const extensionDescription = this._registry.getExtensionDescription(extension.identifier); if (!extensionDescription) { - // ignore removing an extension which is not running + // Can't remove an extension that is unknown! return false; } diff --git a/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts b/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts index 001c0141a27..0277a064de7 100644 --- a/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts +++ b/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts @@ -68,19 +68,16 @@ export class ExtensionDescriptionRegistry { } public deltaExtensions(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): DeltaExtensionsResult { - if (toAdd.length > 0) { - this._extensionDescriptions = this._extensionDescriptions.concat(toAdd); - } + // It is possible that an extension is removed, only to be added again at a different version + // so we will first handle removals + this._extensionDescriptions = removeExtensions(this._extensionDescriptions, toRemove); + + // Then, handle the extensions to add + this._extensionDescriptions = this._extensionDescriptions.concat(toAdd); // Immediately remove looping extensions! const looping = ExtensionDescriptionRegistry._findLoopingExtensions(this._extensionDescriptions); - toRemove = toRemove.concat(looping.map(ext => ext.identifier)); - - if (toRemove.length > 0) { - const toRemoveSet = new Set(); - toRemove.forEach(extensionId => toRemoveSet.add(ExtensionIdentifier.toKey(extensionId))); - this._extensionDescriptions = this._extensionDescriptions.filter(extension => !toRemoveSet.has(ExtensionIdentifier.toKey(extension.identifier))); - } + this._extensionDescriptions = removeExtensions(this._extensionDescriptions, looping.map(ext => ext.identifier)); this._initialize(); this._onDidChange.fire(undefined); @@ -194,6 +191,22 @@ export class ExtensionDescriptionRegistry { const extension = this._extensionsMap.get(ExtensionIdentifier.toKey(extensionId)); return extension ? extension : undefined; } + + public getExtensionDescriptionByUUID(uuid: string): IExtensionDescription | undefined { + for (const extensionDescription of this._extensionsArr) { + if (extensionDescription.uuid === uuid) { + return extensionDescription; + } + } + return undefined; + } + + public getExtensionDescriptionByIdOrUUID(extensionId: ExtensionIdentifier | string, uuid: string | undefined): IExtensionDescription | undefined { + return ( + this.getExtensionDescription(extensionId) + ?? (uuid ? this.getExtensionDescriptionByUUID(uuid) : undefined) + ); + } } const enum SortBucket { @@ -226,3 +239,9 @@ function extensionCmp(a: IExtensionDescription, b: IExtensionDescription): numbe } return 0; } + +function removeExtensions(arr: IExtensionDescription[], toRemove: ExtensionIdentifier[]): IExtensionDescription[] { + const toRemoveSet = new Set(); + toRemove.forEach(extensionId => toRemoveSet.add(ExtensionIdentifier.toKey(extensionId))); + return arr.filter(extension => !toRemoveSet.has(ExtensionIdentifier.toKey(extension.identifier))); +} diff --git a/src/vs/workbench/services/extensions/test/common/extensionDescriptionRegistry.test.ts b/src/vs/workbench/services/extensions/test/common/extensionDescriptionRegistry.test.ts new file mode 100644 index 00000000000..8249f569082 --- /dev/null +++ b/src/vs/workbench/services/extensions/test/common/extensionDescriptionRegistry.test.ts @@ -0,0 +1,40 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { URI } from 'vs/base/common/uri'; +import { ExtensionIdentifier, IExtensionDescription, TargetPlatform } from 'vs/platform/extensions/common/extensions'; +import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; + +suite('ExtensionDescriptionRegistry', () => { + test('allow removing and adding the same extension at a different version', () => { + const idA = new ExtensionIdentifier('a'); + const extensionA1 = desc(idA, '1.0.0'); + const extensionA2 = desc(idA, '2.0.0'); + + const registry = new ExtensionDescriptionRegistry([extensionA1]); + registry.deltaExtensions([extensionA2], [idA]); + + assert.deepStrictEqual(registry.getAllExtensionDescriptions(), [extensionA2]); + }); + + function desc(id: ExtensionIdentifier, version: string, activationEvents: string[] = ['*']): IExtensionDescription { + return { + name: id.value, + publisher: 'test', + version: '0.0.0', + engines: { vscode: '^1.0.0' }, + identifier: id, + extensionLocation: URI.parse(`nothing://nowhere`), + isBuiltin: false, + isUnderDevelopment: false, + isUserBuiltin: false, + activationEvents, + main: 'index.js', + targetPlatform: TargetPlatform.UNDEFINED, + extensionDependencies: [] + }; + } +}); From 13f136fc46863edec7cd523eab43ae9ab5838aa0 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Thu, 30 Jun 2022 02:11:48 -0700 Subject: [PATCH 089/150] Include comments exporting of translations (#153769) Include comments in call to addFile. Fixes #150990 --- build/lib/i18n.js | 17 ++++++++++++----- build/lib/i18n.ts | 17 ++++++++++++----- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/build/lib/i18n.js b/build/lib/i18n.js index dc7c12459b7..13de16ce9a0 100644 --- a/build/lib/i18n.js +++ b/build/lib/i18n.js @@ -599,17 +599,24 @@ function createXlfFilesForExtensions() { const basename = path.basename(file.path); if (basename === 'package.nls.json') { const json = JSON.parse(buffer.toString('utf8')); - const keys = Object.keys(json); - const messages = keys.map((key) => { + const keys = []; + const messages = []; + Object.keys(json).forEach((key) => { const value = json[key]; if (Is.string(value)) { - return value; + keys.push(key); + messages.push(value); } else if (value) { - return value.message; + keys.push({ + key, + comment: value.comment + }); + messages.push(value.message); } else { - return `Unknown message for key: ${key}`; + keys.push(key); + messages.push(`Unknown message for key: ${key}`); } }); getXlf().addFile(`extensions/${extensionName}/package`, keys, messages); diff --git a/build/lib/i18n.ts b/build/lib/i18n.ts index dae24b53679..05b0634120b 100644 --- a/build/lib/i18n.ts +++ b/build/lib/i18n.ts @@ -714,15 +714,22 @@ export function createXlfFilesForExtensions(): ThroughStream { const basename = path.basename(file.path); if (basename === 'package.nls.json') { const json: PackageJsonFormat = JSON.parse(buffer.toString('utf8')); - const keys = Object.keys(json); - const messages = keys.map((key) => { + const keys: Array = []; + const messages: string[] = []; + Object.keys(json).forEach((key) => { const value = json[key]; if (Is.string(value)) { - return value; + keys.push(key); + messages.push(value); } else if (value) { - return value.message; + keys.push({ + key, + comment: value.comment + }); + messages.push(value.message); } else { - return `Unknown message for key: ${key}`; + keys.push(key); + messages.push(`Unknown message for key: ${key}`); } }); getXlf().addFile(`extensions/${extensionName}/package`, keys, messages); From 1230b7c300409cc8d20a1a8613c9174d0998c6d4 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 30 Jun 2022 11:12:31 +0200 Subject: [PATCH 090/150] Git - "Commit & Push" command now publishes the branch if there is no tracking remote (#153749) "Commit & Push" command now publishes the branch if there is no tracking remote --- extensions/git/src/commands.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 052f266eedd..22649e86fc7 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -1625,7 +1625,7 @@ export class CommandCenter { const postCommitCommand = config.get<'none' | 'push' | 'sync'>('postCommitCommand'); if ((opts.postCommitCommand === undefined && postCommitCommand === 'push') || opts.postCommitCommand === 'push') { - await this._push(repository, { pushType: PushType.Push, silent: true }); + await this._push(repository, { pushType: PushType.Push }); } if ((opts.postCommitCommand === undefined && postCommitCommand === 'sync') || opts.postCommitCommand === 'sync') { await this.sync(repository); From d04edca5174f5dc14550a32c535f08107534baa7 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 30 Jun 2022 14:19:52 +0200 Subject: [PATCH 091/150] Adjusts command naming. --- .../contrib/mergeEditor/browser/commands/commands.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts b/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts index 8d4d6ad1e96..4fa542c981d 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts @@ -12,7 +12,7 @@ import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiati import { API_OPEN_DIFF_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; import { MergeEditorInput, MergeEditorInputData } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; import { MergeEditor } from 'vs/workbench/contrib/mergeEditor/browser/view/mergeEditor'; -import { ctxMergeEditorLayout, ctxIsMergeEditor } from 'vs/workbench/contrib/mergeEditor/common/mergeEditor'; +import { ctxIsMergeEditor, ctxMergeEditorLayout } from 'vs/workbench/contrib/mergeEditor/common/mergeEditor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; export class OpenMergeEditor extends Action2 { @@ -210,7 +210,7 @@ export class ToggleActiveConflictInput1 extends Action2 { super({ id: 'merge.toggleActiveConflictInput1', category: localize('mergeEditor', "Merge Editor"), - title: localize('merge.toggleActiveConflictInput1', "Toggle Active Conflict In Input 1"), + title: localize('merge.toggleCurrentConflictFromLeft', "Toggle Current Conflict from Left"), f1: true, precondition: ctxIsMergeEditor, }); @@ -233,7 +233,7 @@ export class ToggleActiveConflictInput2 extends Action2 { super({ id: 'merge.toggleActiveConflictInput2', category: localize('mergeEditor', "Merge Editor"), - title: localize('merge.toggleActiveConflictInput2', "Toggle Active Conflict In Input 2"), + title: localize('merge.toggleCurrentConflictFromRight', "Toggle Current Conflict from Right"), f1: true, precondition: ctxIsMergeEditor, }); From ab670df24b6e4c003bc5163c022caa215cfc3f78 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 30 Jun 2022 14:26:02 +0200 Subject: [PATCH 092/150] disable profiles feature in stable quality (#153796) --- src/vs/code/electron-main/main.ts | 2 +- src/vs/code/node/cliProcessMain.ts | 2 +- .../platform/contextkey/common/contextkeys.ts | 1 + .../userDataProfile/common/userDataProfile.ts | 18 ------------- src/vs/workbench/browser/contextkeys.ts | 7 ++++- .../browser/userDataProfile.contribution.ts | 13 ---------- .../browser/userDataProfile.ts | 26 ++++++++++++++++++- .../userDataProfile/common/userDataProfile.ts | 4 +-- 8 files changed, 36 insertions(+), 37 deletions(-) diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index b52ad7e31b2..1a4f72bd620 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -256,7 +256,7 @@ class CodeMain { configurationService.initialize() ]); - userDataProfilesMainService.setEnablement(configurationService.getValue(PROFILES_ENABLEMENT_CONFIG)); + userDataProfilesMainService.setEnablement(!!configurationService.getValue(PROFILES_ENABLEMENT_CONFIG)); } private async claimInstance(logService: ILogService, environmentMainService: IEnvironmentMainService, lifecycleMainService: ILifecycleMainService, instantiationService: IInstantiationService, productService: IProductService, retry: boolean): Promise { diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index 56ee36e7f1b..bc4ca25f492 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -164,7 +164,7 @@ class CliMain extends Disposable { configurationService.initialize() ]); - userDataProfilesService.setEnablement(configurationService.getValue(PROFILES_ENABLEMENT_CONFIG)); + userDataProfilesService.setEnablement(!!configurationService.getValue(PROFILES_ENABLEMENT_CONFIG)); // URI Identity services.set(IUriIdentityService, new UriIdentityService(fileService)); diff --git a/src/vs/platform/contextkey/common/contextkeys.ts b/src/vs/platform/contextkey/common/contextkeys.ts index 03c40dd332c..de968735c0a 100644 --- a/src/vs/platform/contextkey/common/contextkeys.ts +++ b/src/vs/platform/contextkey/common/contextkeys.ts @@ -16,6 +16,7 @@ export const IsMacNativeContext = new RawContextKey('isMacNative', isMa export const IsIOSContext = new RawContextKey('isIOS', isIOS, localize('isIOS', "Whether the operating system is iOS")); export const IsDevelopmentContext = new RawContextKey('isDevelopment', false, true); +export const ProductQualityContext = new RawContextKey('productQualityType', '', localize('productQualityType', "Quality type of VS Code")); export const InputFocusedContextKey = 'inputFocus'; export const InputFocusedContext = new RawContextKey(InputFocusedContextKey, false, localize('inputFocus', "Whether keyboard focus is inside an input box")); diff --git a/src/vs/platform/userDataProfile/common/userDataProfile.ts b/src/vs/platform/userDataProfile/common/userDataProfile.ts index ede2d98a33e..0705d48af4f 100644 --- a/src/vs/platform/userDataProfile/common/userDataProfile.ts +++ b/src/vs/platform/userDataProfile/common/userDataProfile.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { hash } from 'vs/base/common/hash'; -import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { joinPath } from 'vs/base/common/resources'; @@ -16,8 +15,6 @@ import { IFileService } from 'vs/platform/files/common/files'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { isWeb } from 'vs/base/common/platform'; /** * Flags to indicate whether to use the default profile or not. @@ -65,21 +62,6 @@ export function isUserDataProfile(thing: unknown): thing is IUserDataProfile { } export const PROFILES_ENABLEMENT_CONFIG = 'workbench.experimental.settingsProfiles.enabled'; -export const PROFILES_ENABLEMENT_CONFIG_SCHEMA: IConfigurationPropertySchema = { - 'type': 'boolean', - 'default': false, - 'description': localize('workbench.experimental.settingsProfiles.enabled', "Controls whether to enable the Settings Profiles preview feature."), - scope: ConfigurationScope.APPLICATION -}; - -if (!isWeb) { - // Registering here so that the configuration is read properly in main and cli processes. - Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ - 'properties': { - [PROFILES_ENABLEMENT_CONFIG]: PROFILES_ENABLEMENT_CONFIG_SCHEMA - } - }); -} export type EmptyWindowWorkspaceIdentifier = 'empty-window'; export type WorkspaceIdentifier = ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier | EmptyWindowWorkspaceIdentifier; diff --git a/src/vs/workbench/browser/contextkeys.ts b/src/vs/workbench/browser/contextkeys.ts index df9b2c329d5..3d287e57781 100644 --- a/src/vs/workbench/browser/contextkeys.ts +++ b/src/vs/workbench/browser/contextkeys.ts @@ -6,7 +6,7 @@ import { Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { InputFocusedContext, IsMacContext, IsLinuxContext, IsWindowsContext, IsWebContext, IsMacNativeContext, IsDevelopmentContext, IsIOSContext } from 'vs/platform/contextkey/common/contextkeys'; +import { InputFocusedContext, IsMacContext, IsLinuxContext, IsWindowsContext, IsWebContext, IsMacNativeContext, IsDevelopmentContext, IsIOSContext, ProductQualityContext } from 'vs/platform/contextkey/common/contextkeys'; import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext } from 'vs/workbench/common/contextkeys'; import { TEXT_DIFF_EDITOR_ID, EditorInputCapabilities, SIDE_BY_SIDE_EDITOR_ID, DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor'; import { trackFocus, addDisposableListener, EventType } from 'vs/base/browser/dom'; @@ -24,6 +24,7 @@ import { IEditorResolverService } from 'vs/workbench/services/editor/common/edit import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; import { Schemas } from 'vs/base/common/network'; import { WebFileSystemAccess } from 'vs/platform/files/browser/webFileSystemAccess'; +import { IProductService } from 'vs/platform/product/common/productService'; export class WorkbenchContextKeysHandler extends Disposable { private inputFocusedContext: IContextKey; @@ -76,6 +77,7 @@ export class WorkbenchContextKeysHandler extends Disposable { @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IConfigurationService private readonly configurationService: IConfigurationService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IProductService private readonly productService: IProductService, @IEditorService private readonly editorService: IEditorService, @IEditorResolverService private readonly editorResolverService: IEditorResolverService, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @@ -105,6 +107,9 @@ export class WorkbenchContextKeysHandler extends Disposable { // Development IsDevelopmentContext.bindTo(this.contextKeyService).set(!this.environmentService.isBuilt || this.environmentService.isExtensionDevelopment); + // Product Quality + ProductQualityContext.bindTo(this.contextKeyService).set(this.productService.quality || ''); + // Editors this.activeEditorContext = ActiveEditorContext.bindTo(this.contextKeyService); this.activeEditorIsReadonly = ActiveEditorReadonlyContext.bindTo(this.contextKeyService); diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.contribution.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.contribution.ts index eee76405345..ae8e9bbaf5d 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.contribution.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.contribution.ts @@ -3,24 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { isWeb } from 'vs/base/common/platform'; -import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; -import { PROFILES_ENABLEMENT_CONFIG, PROFILES_ENABLEMENT_CONFIG_SCHEMA } from 'vs/platform/userDataProfile/common/userDataProfile'; -import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; import { IWorkbenchContributionsRegistry, Extensions } from 'vs/workbench/common/contributions'; import { UserDataProfilesWorkbenchContribution } from 'vs/workbench/contrib/userDataProfile/browser/userDataProfile'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import '../common/userDataProfileActions'; -if (!isWeb) { - Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ - ...workbenchConfigurationNodeBase, - 'properties': { - [PROFILES_ENABLEMENT_CONFIG]: PROFILES_ENABLEMENT_CONFIG_SCHEMA - } - }); -} - const workbenchRegistry = Registry.as(Extensions.Workbench); workbenchRegistry.registerWorkbenchContribution(UserDataProfilesWorkbenchContribution, LifecyclePhase.Ready); diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts index ad743794165..8a75af540d3 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts @@ -6,14 +6,19 @@ import { Codicon } from 'vs/base/common/codicons'; import { Event } from 'vs/base/common/event'; import { Disposable, DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import { isWeb } from 'vs/base/common/platform'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { localize } from 'vs/nls'; import { Action2, ISubmenuItem, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { Registry } from 'vs/platform/registry/common/platform'; import { registerColor } from 'vs/platform/theme/common/colorRegistry'; import { themeColorFromId } from 'vs/platform/theme/common/themeService'; -import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUserDataProfile, IUserDataProfilesService, PROFILES_ENABLEMENT_CONFIG } from 'vs/platform/userDataProfile/common/userDataProfile'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/browser/statusbar'; import { IUserDataProfileManagementService, IUserDataProfileService, ManageProfilesSubMenu, PROFILES_CATEGORY, PROFILES_ENABLEMENT_CONTEXT, PROFILES_TTILE } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; @@ -30,10 +35,13 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements @IUserDataProfileManagementService private readonly userDataProfileManagementService: IUserDataProfileManagementService, @IStatusbarService private readonly statusBarService: IStatusbarService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, + @IProductService private readonly productService: IProductService, @IContextKeyService contextKeyService: IContextKeyService, ) { super(); + this.registerConfiguration(); + this.currentProfileContext = CONTEXT_CURRENT_PROFILE.bindTo(contextKeyService); this.currentProfileContext.set(this.userDataProfileService.currentProfile.id); this._register(this.userDataProfileService.onDidChangeCurrentProfile(e => this.currentProfileContext.set(this.userDataProfileService.currentProfile.id))); @@ -44,6 +52,22 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements this.registerActions(); } + private registerConfiguration(): void { + if (!isWeb && this.productService.quality !== 'stable') { + Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ + ...workbenchConfigurationNodeBase, + 'properties': { + [PROFILES_ENABLEMENT_CONFIG]: { + 'type': 'boolean', + 'default': false, + 'description': localize('workbench.experimental.settingsProfiles.enabled', "Controls whether to enable the Settings Profiles preview feature."), + scope: ConfigurationScope.APPLICATION + } + } + }); + } + } + private registerActions(): void { this.registerManageProfilesSubMenu(); diff --git a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts index d1ea95eef66..a293ec712b1 100644 --- a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts +++ b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts @@ -10,7 +10,7 @@ import { MenuId } from 'vs/platform/actions/common/actions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IUserDataProfile, PROFILES_ENABLEMENT_CONFIG, UseDefaultProfileFlags } from 'vs/platform/userDataProfile/common/userDataProfile'; import { ContextKeyDefinedExpr, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys'; +import { IsWebContext, ProductQualityContext } from 'vs/platform/contextkey/common/contextkeys'; export interface DidChangeUserDataProfileEvent { readonly preserveData: boolean; @@ -72,4 +72,4 @@ export const PROFILES_TTILE = { value: localize('settings profiles', "Settings P export const PROFILES_CATEGORY = PROFILES_TTILE.value; export const PROFILE_EXTENSION = 'code-profile'; export const PROFILE_FILTER = [{ name: localize('profile', "Settings Profile"), extensions: [PROFILE_EXTENSION] }]; -export const PROFILES_ENABLEMENT_CONTEXT = ContextKeyExpr.and(IsWebContext.negate(), ContextKeyDefinedExpr.create(`config.${PROFILES_ENABLEMENT_CONFIG}`)); +export const PROFILES_ENABLEMENT_CONTEXT = ContextKeyExpr.and(ProductQualityContext.notEqualsTo('stable'), IsWebContext.negate(), ContextKeyDefinedExpr.create(`config.${PROFILES_ENABLEMENT_CONFIG}`)); From cc2593c288f2daf7b7870571fffd3134435a00a1 Mon Sep 17 00:00:00 2001 From: isidor Date: Thu, 30 Jun 2022 15:09:07 +0200 Subject: [PATCH 093/150] fixes #152430 --- src/vs/workbench/browser/workbench.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index fd5b98e1620..9c75b526575 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -96,7 +96,7 @@ const registry = Registry.as(ConfigurationExtensions.Con 'type': 'string', 'enum': ['text', 'hidden'], 'default': 'text', - 'markdownDescription': localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'untitledHint' }, "Controls if the untitled hint should be inline text in the editor or a floating button or hidden.") + 'markdownDescription': localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'untitledHint' }, "Controls if the untitled text hint should be visible in the editor.") }, 'workbench.editor.languageDetection': { type: 'boolean', From cf532ac7db71b93c1150e74ae5a206bdbf29e0a7 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 30 Jun 2022 15:42:23 +0200 Subject: [PATCH 094/150] Git - Tweak the git.useEditorAsCommitInput setting description (#153794) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Tweak the setting description * Update extensions/git/package.nls.json Co-authored-by: João Moreno Co-authored-by: João Moreno --- extensions/git/package.nls.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index c5b11a7b85a..2477a55fa76 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -142,7 +142,7 @@ "config.ignoreLimitWarning": "Ignores the warning when there are too many changes in a repository.", "config.ignoreRebaseWarning": "Ignores the warning when it looks like the branch might have been rebased when pulling.", "config.defaultCloneDirectory": "The default location to clone a git repository.", - "config.useEditorAsCommitInput": "Use an editor to author the commit message.", + "config.useEditorAsCommitInput": "Controls whether a full text editor will be used to author commit messages, whenever no message is provided in the commit input box.", "config.verboseCommit": "Enable verbose output when `#git.useEditorAsCommitInput#` is enabled.", "config.enableSmartCommit": "Commit all changes when there are no staged changes.", "config.smartCommitChanges": "Control which changes are automatically staged by Smart Commit.", From 35a5a6f484fc2d9aebe0fffac15f92c972e4a445 Mon Sep 17 00:00:00 2001 From: Robo Date: Thu, 30 Jun 2022 23:03:22 +0900 Subject: [PATCH 095/150] chore: update distro (#153818) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 607e5058946..0c567bb015c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.69.0", - "distro": "a44d562925abc5db8eb21089d4a69ae544b3ec69", + "distro": "daf000367ee34d716c5dbdf836b11c06c407ef5f", "author": { "name": "Microsoft Corporation" }, From 36351641a2051c81cc0134744e2983c111c27367 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 30 Jun 2022 16:08:07 +0200 Subject: [PATCH 096/150] Fix #153171 (#153820) --- .../browser/extensionEnablementService.ts | 82 +++++++++++++------ .../extensionEnablementService.test.ts | 32 ++++++++ 2 files changed, 90 insertions(+), 24 deletions(-) diff --git a/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts b/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts index b72ed20cef2..7c9d42c8621 100644 --- a/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts +++ b/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts @@ -147,7 +147,11 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench throw new Error(localize('cannot change enablement environment', "Cannot change enablement of {0} extension because it is enabled in environment", extension.manifest.displayName || extension.identifier.id)); } - switch (this.getEnablementState(extension)) { + this.throwErrorIfEnablementStateCannotBeChanged(extension, this.getEnablementState(extension), donotCheckDependencies); + } + + private throwErrorIfEnablementStateCannotBeChanged(extension: IExtension, enablementStateOfExtension: EnablementState, donotCheckDependencies?: boolean): void { + switch (enablementStateOfExtension) { case EnablementState.DisabledByEnvironment: throw new Error(localize('cannot change disablement environment', "Cannot change enablement of {0} extension because it is disabled in environment", extension.manifest.displayName || extension.identifier.id)); case EnablementState.DisabledByVirtualWorkspace: @@ -219,30 +223,60 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench } private getExtensionsToEnableRecursively(extensions: IExtension[], allExtensions: ReadonlyArray, enablementState: EnablementState, options: { dependencies: boolean; pack: boolean }, checked: IExtension[] = []): IExtension[] { - const toCheck = extensions.filter(e => checked.indexOf(e) === -1); - if (toCheck.length) { - for (const extension of toCheck) { - checked.push(extension); - } - const extensionsToDisable = allExtensions.filter(i => { - if (checked.indexOf(i) !== -1) { - return false; - } - if (this.getEnablementState(i) === enablementState) { - return false; - } - return (options.dependencies || options.pack) - && extensions.some(extension => - (options.dependencies && extension.manifest.extensionDependencies?.some(id => areSameExtensions({ id }, i.identifier))) - || (options.pack && extension.manifest.extensionPack?.some(id => areSameExtensions({ id }, i.identifier))) - ); - }); - if (extensionsToDisable.length) { - extensionsToDisable.push(...this.getExtensionsToEnableRecursively(extensionsToDisable, allExtensions, enablementState, options, checked)); - } - return extensionsToDisable; + if (!options.dependencies && !options.pack) { + return []; } - return []; + + const toCheck = extensions.filter(e => checked.indexOf(e) === -1); + if (!toCheck.length) { + return []; + } + + for (const extension of toCheck) { + checked.push(extension); + } + + const extensionsToDisable: IExtension[] = []; + for (const extension of allExtensions) { + // Extension is already checked + if (checked.some(e => areSameExtensions(e.identifier, extension.identifier))) { + continue; + } + + const enablementStateOfExtension = this.getEnablementState(extension); + // Extension enablement state is same as the end enablement state + if (enablementStateOfExtension === enablementState) { + continue; + } + + // Check if the extension is a dependency or in extension pack + if (extensions.some(e => + (options.dependencies && e.manifest.extensionDependencies?.some(id => areSameExtensions({ id }, extension.identifier))) + || (options.pack && e.manifest.extensionPack?.some(id => areSameExtensions({ id }, extension.identifier))))) { + + const index = extensionsToDisable.findIndex(e => areSameExtensions(e.identifier, extension.identifier)); + + // Extension is not aded to the disablement list so add it + if (index === -1) { + extensionsToDisable.push(extension); + } + + // Extension is there already in the disablement list. + else { + try { + // Replace only if the enablement state can be changed + this.throwErrorIfEnablementStateCannotBeChanged(extension, enablementStateOfExtension, true); + extensionsToDisable.splice(index, 1, extension); + } catch (error) { /*Do not add*/ } + } + } + } + + if (extensionsToDisable.length) { + extensionsToDisable.push(...this.getExtensionsToEnableRecursively(extensionsToDisable, allExtensions, enablementState, options, checked)); + } + + return extensionsToDisable; } private _setUserEnablementState(extension: IExtension, newState: EnablementState): Promise { 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 0f5aacd40a3..1bfaa725a8a 100644 --- a/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts +++ b/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts @@ -624,6 +624,38 @@ suite('ExtensionEnablementService Test', () => { assert.deepStrictEqual(testObject.getEnablementState(extension), EnablementState.DisabledByVirtualWorkspace); }); + test('test enable a remote workspace extension and local ui extension that is a dependency of remote', async () => { + instantiationService.stub(IExtensionManagementServerService, anExtensionManagementServerService(anExtensionManagementServer('vscode-local', instantiationService), anExtensionManagementServer('vscode-remote', instantiationService), null)); + const localUIExtension = aLocalExtension2('pub.a', { main: 'main.js', extensionKind: ['ui'] }, { location: URI.file(`pub.a`) }); + const remoteUIExtension = aLocalExtension2('pub.a', { main: 'main.js', extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: 'vscode-remote' }) }); + const target = aLocalExtension2('pub.b', { main: 'main.js', extensionDependencies: ['pub.a'] }, { location: URI.file(`pub.b`).with({ scheme: 'vscode-remote' }) }); + testObject = new TestExtensionEnablementService(instantiationService); + + installed.push(localUIExtension, remoteUIExtension, target); + await testObject.setEnablement([target, localUIExtension], EnablementState.DisabledGlobally); + await testObject.setEnablement([target, localUIExtension], EnablementState.EnabledGlobally); + assert.ok(testObject.isEnabled(target)); + assert.ok(testObject.isEnabled(localUIExtension)); + assert.strictEqual(testObject.getEnablementState(target), EnablementState.EnabledGlobally); + assert.strictEqual(testObject.getEnablementState(localUIExtension), EnablementState.EnabledGlobally); + }); + + test('test enable a remote workspace extension also enables its dependency in local', async () => { + instantiationService.stub(IExtensionManagementServerService, anExtensionManagementServerService(anExtensionManagementServer('vscode-local', instantiationService), anExtensionManagementServer('vscode-remote', instantiationService), null)); + const localUIExtension = aLocalExtension2('pub.a', { main: 'main.js', extensionKind: ['ui'] }, { location: URI.file(`pub.a`) }); + const remoteUIExtension = aLocalExtension2('pub.a', { main: 'main.js', extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: 'vscode-remote' }) }); + const target = aLocalExtension2('pub.b', { main: 'main.js', extensionDependencies: ['pub.a'] }, { location: URI.file(`pub.b`).with({ scheme: 'vscode-remote' }) }); + testObject = new TestExtensionEnablementService(instantiationService); + + installed.push(localUIExtension, remoteUIExtension, target); + await testObject.setEnablement([target, localUIExtension], EnablementState.DisabledGlobally); + await testObject.setEnablement([target], EnablementState.EnabledGlobally); + assert.ok(testObject.isEnabled(target)); + assert.ok(testObject.isEnabled(localUIExtension)); + assert.strictEqual(testObject.getEnablementState(target), EnablementState.EnabledGlobally); + assert.strictEqual(testObject.getEnablementState(localUIExtension), EnablementState.EnabledGlobally); + }); + test('test canChangeEnablement return false when extension is disabled in virtual workspace', () => { const extension = aLocalExtension2('pub.a', { capabilities: { virtualWorkspaces: false } }); instantiationService.stub(IWorkspaceContextService, 'getWorkspace', { folders: [{ uri: URI.file('worskapceA').with(({ scheme: 'virtual' })) }] }); From e1425022964548f5e04504c2bae62937e392d4e3 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 30 Jun 2022 16:23:01 +0200 Subject: [PATCH 097/150] Makes "Remaining Conflicts" localizable. --- .../browser/view/editors/resultCodeEditorView.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts index 1bdd79fb0e1..fc6919af08a 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts @@ -5,6 +5,7 @@ import { CompareResult } from 'vs/base/common/arrays'; import { IModelDeltaDecoration, MinimapPosition, OverviewRulerLane } from 'vs/editor/common/model'; +import { localize } from 'vs/nls'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { autorun, derivedObservable } from 'vs/workbench/contrib/audioCues/browser/observable'; import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange'; @@ -113,8 +114,17 @@ export class ResultCodeEditorView extends CodeEditorView { } const count = model.unhandledConflictsCount.read(reader); - // TODO @joh - this._detail.setLabel(`${count} Remaining Conflicts`); + this._detail.setLabel(count === 1 + ? localize( + 'mergeEditor.remainingConflicts', + '{0} Remaining Conflict', + count + ) + : localize( + 'mergeEditor.remainingConflict', + '{0} Remaining Conflicts', + count + )); }, 'update label')); } } From 1c793dfac8b6da321ffd498a530332ebe3b740b8 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 30 Jun 2022 16:24:02 +0200 Subject: [PATCH 098/150] Adds menu separator. --- .../mergeEditor/browser/view/editors/inputCodeEditorView.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts index 74452e55aeb..e65d5c9c8ef 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts @@ -5,7 +5,7 @@ import * as dom from 'vs/base/browser/dom'; import { Toggle } from 'vs/base/browser/ui/toggle/toggle'; -import { Action, IAction } from 'vs/base/common/actions'; +import { Action, IAction, Separator } from 'vs/base/common/actions'; import { Codicon } from 'vs/base/common/codicons'; import { Disposable } from 'vs/base/common/lifecycle'; import { noBreakWhitespace } from 'vs/base/common/strings'; @@ -185,6 +185,7 @@ export class InputCodeEditorView extends CodeEditorView { { enabled: baseRange.canBeCombined } ) : undefined, + new Separator(), baseRange.isConflicting ? setFields( action( From b18f9e06306d744fb5229e048b87eda183b19fe4 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 30 Jun 2022 16:27:42 +0200 Subject: [PATCH 099/150] Git - Do not show Sync Changes and Publish Branch action button when commit is in progress (#153821) Do not show Sync Changes and Publish Branch action button when commit is in progress --- extensions/git/src/actionButton.ts | 43 ++++++++++++++++-------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/extensions/git/src/actionButton.ts b/extensions/git/src/actionButton.ts index f62cb214879..7fa03831f2f 100644 --- a/extensions/git/src/actionButton.ts +++ b/extensions/git/src/actionButton.ts @@ -13,7 +13,8 @@ const localize = nls.loadMessageBundle(); interface ActionButtonState { readonly HEAD: Branch | undefined; - readonly isActionRunning: boolean; + readonly isCommitInProgress: boolean; + readonly isSyncInProgress: boolean; readonly repositoryHasChanges: boolean; } @@ -33,7 +34,7 @@ export class ActionButtonCommand { private disposables: Disposable[] = []; constructor(readonly repository: Repository) { - this._state = { HEAD: undefined, isActionRunning: false, repositoryHasChanges: false }; + this._state = { HEAD: undefined, isCommitInProgress: false, isSyncInProgress: false, repositoryHasChanges: false }; repository.onDidRunGitStatus(this.onDidRunGitStatus, this, this.disposables); repository.onDidChangeOperations(this.onDidChangeOperations, this, this.disposables); @@ -75,21 +76,21 @@ export class ActionButtonCommand { switch (postCommitCommand) { case 'push': { title = localize('scm button commit and push title', "$(arrow-up) Commit & Push"); - tooltip = this.state.isActionRunning ? + tooltip = this.state.isCommitInProgress ? localize('scm button committing pushing tooltip', "Committing & Pushing Changes...") : localize('scm button commit push tooltip', "Commit & Push Changes"); break; } case 'sync': { title = localize('scm button commit and sync title', "$(sync) Commit & Sync"); - tooltip = this.state.isActionRunning ? + tooltip = this.state.isCommitInProgress ? localize('scm button committing synching tooltip', "Committing & Synching Changes...") : localize('scm button commit sync tooltip', "Commit & Sync Changes"); break; } default: { title = localize('scm button commit title', "$(check) Commit"); - tooltip = this.state.isActionRunning ? + tooltip = this.state.isCommitInProgress ? localize('scm button committing tooltip', "Committing Changes...") : localize('scm button commit tooltip', "Commit Changes"); break; @@ -122,7 +123,7 @@ export class ActionButtonCommand { }, ] ], - enabled: this.state.repositoryHasChanges && !this.state.isActionRunning + enabled: this.state.repositoryHasChanges && !this.state.isCommitInProgress }; } @@ -130,19 +131,19 @@ export class ActionButtonCommand { const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); const showActionButton = config.get<{ publish: boolean }>('showActionButton', { publish: true }); - // Branch does have an upstream or the button is disabled - if (this.state.HEAD?.upstream || !showActionButton.publish) { return undefined; } + // Branch does have an upstream, commit is in progress, or the button is disabled + if (this.state.HEAD?.upstream || this.state.isCommitInProgress || !showActionButton.publish) { return undefined; } return { command: { command: 'git.publish', title: localize('scm publish branch action button title', "{0} Publish Branch", '$(cloud-upload)'), - tooltip: this.state.isActionRunning ? + tooltip: this.state.isSyncInProgress ? localize('scm button publish branch running', "Publishing Branch...") : localize('scm button publish branch', "Publish Branch"), arguments: [this.repository.sourceControl], }, - enabled: !this.state.isActionRunning + enabled: !this.state.isSyncInProgress }; } @@ -150,12 +151,12 @@ export class ActionButtonCommand { const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); const showActionButton = config.get<{ sync: boolean }>('showActionButton', { sync: true }); - // Branch does not have an upstream or the button is disabled - if (!this.state.HEAD?.upstream || !showActionButton.sync) { return undefined; } + // Branch does not have an upstream, commit is in progress, or the button is disabled + if (!this.state.HEAD?.upstream || this.state.isCommitInProgress || !showActionButton.sync) { return undefined; } const ahead = this.state.HEAD.ahead ? ` ${this.state.HEAD.ahead}$(arrow-up)` : ''; const behind = this.state.HEAD.behind ? ` ${this.state.HEAD.behind}$(arrow-down)` : ''; - const icon = this.state.isActionRunning ? '$(sync~spin)' : '$(sync)'; + const icon = this.state.isSyncInProgress ? '$(sync~spin)' : '$(sync)'; const rebaseWhenSync = config.get('rebaseWhenSync'); @@ -163,24 +164,26 @@ export class ActionButtonCommand { command: { command: rebaseWhenSync ? 'git.syncRebase' : 'git.sync', title: `${icon}${behind}${ahead}`, - tooltip: this.state.isActionRunning ? + tooltip: this.state.isSyncInProgress ? localize('syncing changes', "Synchronizing Changes...") : this.repository.syncTooltip, arguments: [this.repository.sourceControl], }, description: localize('scm button sync description', "{0} Sync Changes{1}{2}", icon, behind, ahead), - enabled: !this.state.isActionRunning + enabled: !this.state.isSyncInProgress }; } private onDidChangeOperations(): void { - const isActionRunning = - this.repository.operations.isRunning(Operation.Sync) || - this.repository.operations.isRunning(Operation.Push) || - this.repository.operations.isRunning(Operation.Pull) || + const isCommitInProgress = this.repository.operations.isRunning(Operation.Commit); - this.state = { ...this.state, isActionRunning }; + const isSyncInProgress = + this.repository.operations.isRunning(Operation.Sync) || + this.repository.operations.isRunning(Operation.Push) || + this.repository.operations.isRunning(Operation.Pull); + + this.state = { ...this.state, isCommitInProgress, isSyncInProgress }; } private onDidRunGitStatus(): void { From 018d2e361e883f3423fc693e67a760c95a9cfa52 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 30 Jun 2022 16:37:01 +0200 Subject: [PATCH 100/150] Fix #152086 (#153822) --- .../contrib/extensions/browser/extensionsViews.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index a92b512f27a..1ed384b4e22 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -176,7 +176,12 @@ export class ExtensionsListView extends ViewPane { horizontalScrolling: false, accessibilityProvider: >{ getAriaLabel(extension: IExtension | null): string { - return extension ? localize('extension.arialabel', "{0}, {1}, {2}, {3}", extension.displayName, extension.version, extension.publisherDisplayName, extension.description) : ''; + if (!extension) { + return ''; + } + const publisher = localize('extension.arialabel.publihser', "Publisher {0}", extension.publisherDisplayName); + const deprecated = extension?.deprecationInfo ? localize('extension.arialabel.deprecated', "Deprecated") : ''; + return `${extension.displayName}, ${deprecated ? `${deprecated}, ` : ''}${extension.version}, ${publisher}, ${extension.description}`; }, getWidgetAriaLabel(): string { return localize('extensions', "Extensions"); From c82dacd0b4a2fd4d88ad30338749955192fc8cb3 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 30 Jun 2022 07:39:25 -0700 Subject: [PATCH 101/150] Correct id type, inherit id/color individually from parent Fixes #153490 --- src/vs/workbench/api/browser/mainThreadTask.ts | 2 +- .../contrib/tasks/browser/terminalTaskSystem.ts | 9 +++++++-- src/vs/workbench/contrib/tasks/common/tasks.ts | 4 ++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadTask.ts b/src/vs/workbench/api/browser/mainThreadTask.ts index 60cb8a79824..69e679cc5b4 100644 --- a/src/vs/workbench/api/browser/mainThreadTask.ts +++ b/src/vs/workbench/api/browser/mainThreadTask.ts @@ -342,7 +342,7 @@ namespace TaskDTO { return result; } - export function to(task: ITaskDTO | undefined, workspace: IWorkspaceContextService, executeOnly: boolean, icon?: { id: string; color?: string }): ContributedTask | undefined { + export function to(task: ITaskDTO | undefined, workspace: IWorkspaceContextService, executeOnly: boolean, icon?: { id?: string; color?: string }): ContributedTask | undefined { if (!task || (typeof task.name !== 'string')) { return undefined; } diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index ad1971db84f..9b1b4aa4346 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -512,7 +512,12 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { for (const dependency of task.configurationProperties.dependsOn) { const dependencyTask = await resolver.resolve(dependency.uri, dependency.task!); if (dependencyTask) { - dependencyTask.configurationProperties.icon = dependencyTask.configurationProperties.icon || task.configurationProperties.icon; + if (dependencyTask.configurationProperties.icon) { + dependencyTask.configurationProperties.icon.id ||= task.configurationProperties.icon?.id; + dependencyTask.configurationProperties.icon.color ||= task.configurationProperties.icon?.color; + } else { + dependencyTask.configurationProperties.icon = task.configurationProperties.icon; + } const key = dependencyTask.getMapKey(); let promise = this._activeTasks[key] ? this._getDependencyPromise(this._activeTasks[key]) : undefined; if (!promise) { @@ -1037,7 +1042,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { remoteAuthority: this._environmentService.remoteAuthority }); let icon: URI | ThemeIcon | { light: URI; dark: URI } | undefined; - if (task.configurationProperties.icon) { + if (task.configurationProperties.icon?.id) { icon = ThemeIcon.fromId(task.configurationProperties.icon.id); } else { const taskGroupKind = task.configurationProperties.group ? GroupKind.to(task.configurationProperties.group) : undefined; diff --git a/src/vs/workbench/contrib/tasks/common/tasks.ts b/src/vs/workbench/contrib/tasks/common/tasks.ts index 1f366dfb6d9..1b52c33d02a 100644 --- a/src/vs/workbench/contrib/tasks/common/tasks.ts +++ b/src/vs/workbench/contrib/tasks/common/tasks.ts @@ -548,7 +548,7 @@ export interface IConfigurationProperties { /** * The icon for this task in the terminal tabs list */ - icon?: { id: string; color?: string }; + icon?: { id?: string; color?: string }; } export enum RunOnOptions { @@ -912,7 +912,7 @@ export class ContributedTask extends CommonTask { /** * The icon for the task */ - icon: { id: string; color?: string } | undefined; + icon: { id?: string; color?: string } | undefined; public constructor(id: string, source: IExtensionTaskSource, label: string, type: string | undefined, defines: KeyedTaskIdentifier, command: ICommandConfiguration, hasDefinedMatchers: boolean, runOptions: IRunOptions, From dd2c0a0da27335a62d2a2dd6db72bc81a39f03cf Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 30 Jun 2022 16:45:19 +0200 Subject: [PATCH 102/150] Fixes #153715 - tweaks checkbox theme colors. --- .../browser/view/editors/inputCodeEditorView.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts index e65d5c9c8ef..1610a9a0b0d 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts @@ -16,6 +16,8 @@ import { CodeLensContribution } from 'vs/editor/contrib/codelens/browser/codelen import { localize } from 'vs/nls'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { attachToggleStyler } from 'vs/platform/theme/common/styler'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; import { autorun, derivedObservable, IObservable, ITransaction, ObservableValue, transaction } from 'vs/workbench/contrib/audioCues/browser/observable'; import { InputState, ModifiedBaseRangeState } from 'vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange'; import { applyObservableDecorations, setFields } from 'vs/workbench/contrib/mergeEditor/browser/utils'; @@ -101,6 +103,7 @@ export class InputCodeEditorView extends CodeEditorView { public readonly inputNumber: 1 | 2, @IInstantiationService instantiationService: IInstantiationService, @IContextMenuService contextMenuService: IContextMenuService, + @IThemeService themeService: IThemeService, ) { super(instantiationService); @@ -216,7 +219,7 @@ export class InputCodeEditorView extends CodeEditorView { } })); }, - createView: (item, target) => new MergeConflictGutterItemView(item, target, contextMenuService), + createView: (item, target) => new MergeConflictGutterItemView(item, target, contextMenuService, themeService), }) ); } @@ -240,6 +243,7 @@ export class MergeConflictGutterItemView extends Disposable implements IGutterIt item: ModifiedBaseRangeGutterItemInfo, private readonly target: HTMLElement, contextMenuService: IContextMenuService, + themeService: IThemeService ) { super(); @@ -248,6 +252,9 @@ export class MergeConflictGutterItemView extends Disposable implements IGutterIt target.classList.add('merge-accept-gutter-marker'); const checkBox = new Toggle({ isChecked: false, title: localize('accept', "Accept"), icon: Codicon.check }); + + this._register(attachToggleStyler(checkBox, themeService)); + this._register( dom.addDisposableListener(checkBox.domNode, dom.EventType.MOUSE_DOWN, (e) => { if (e.button === 2) { From 671bd087d0bc89d92661d53bd5de50d8c6ed4edc Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 30 Jun 2022 17:01:15 +0200 Subject: [PATCH 103/150] SCM - Fix issue with focusing the action button (#153825) Fix issue with focusing the action button --- .../contrib/scm/browser/scmViewPane.ts | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 4d9461ae8aa..1a733fb7680 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -108,6 +108,8 @@ class ActionButtonRenderer implements ICompressibleTreeRenderer(); + constructor( @ICommandService private commandService: ICommandService, @IContextMenuService private contextMenuService: IContextMenuService, @@ -131,13 +133,25 @@ class ActionButtonRenderer implements ICompressibleTreeRenderer, index: number, templateData: ActionButtonTemplate, height: number | undefined): void { templateData.disposable.dispose(); + const disposables = new DisposableStore(); + const actionButton = node.element; templateData.actionButton.setButton(node.element.button); + + // Remember action button + this.actionButtons.set(actionButton, templateData.actionButton); + disposables.add({ dispose: () => this.actionButtons.delete(actionButton) }); + + templateData.disposable = disposables; } renderCompressedElements(): void { throw new Error('Should never happen since node is incompressible'); } + focusActionButton(actionButton: ISCMActionButton): void { + this.actionButtons.get(actionButton)?.focus(); + } + disposeElement(node: ITreeNode, index: number, template: ActionButtonTemplate): void { template.disposable.dispose(); } @@ -2200,6 +2214,7 @@ export class SCMViewPane extends ViewPane { get viewModel(): ViewModel { return this._viewModel; } private listLabels!: ResourceLabels; private inputRenderer!: InputRenderer; + private actionButtonRenderer!: ActionButtonRenderer; private readonly disposables = new DisposableStore(); constructor( @@ -2254,6 +2269,8 @@ export class SCMViewPane extends ViewPane { this.inputRenderer = this.instantiationService.createInstance(InputRenderer, this.layoutCache, overflowWidgetsDomNode, (input, height) => this.tree.updateElementHeight(input, height)); const delegate = new ListDelegate(this.inputRenderer); + this.actionButtonRenderer = this.instantiationService.createInstance(ActionButtonRenderer); + this.listLabels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility }); this._register(this.listLabels); @@ -2264,7 +2281,7 @@ export class SCMViewPane extends ViewPane { const renderers: ICompressibleTreeRenderer[] = [ this.instantiationService.createInstance(RepositoryRenderer, getActionViewItemProvider(this.instantiationService)), this.inputRenderer, - this.instantiationService.createInstance(ActionButtonRenderer), + this.actionButtonRenderer, this.instantiationService.createInstance(ResourceGroupRenderer, getActionViewItemProvider(this.instantiationService)), this._register(this.instantiationService.createInstance(ResourceRenderer, () => this._viewModel, this.listLabels, getActionViewItemProvider(this.instantiationService), actionRunner)) ]; @@ -2394,6 +2411,7 @@ export class SCMViewPane extends ViewPane { return; } else if (isSCMActionButton(e.element)) { this.scmViewService.focus(e.element.repository); + this.actionButtonRenderer.focusActionButton(e.element); return; } @@ -2663,6 +2681,10 @@ export class SCMActionButton implements IDisposable { this.disposables.value!.add(attachButtonStyler(this.button, this.themeService)); } + focus(): void { + this.button?.focus(); + } + private clear(): void { this.disposables.value = new DisposableStore(); this.button = undefined; From f24d09f05a9759facb8d3dcd2ebe004d39edfeaf Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Thu, 30 Jun 2022 10:02:35 -0700 Subject: [PATCH 104/150] testing: fix layout issues for long test labels (#153838) Fixes https://github.com/microsoft/vscode/issues/152959 Fixes https://github.com/microsoft/vscode/issues/152622 Some regressions caused by a PR (adding pre-wrap in an attempt to space icons). Instead use flex gap, and fixup sizing for the actions --- .../workbench/contrib/testing/browser/media/testing.css | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/testing/browser/media/testing.css b/src/vs/workbench/contrib/testing/browser/media/testing.css index 7f42e463ff6..d93803367a4 100644 --- a/src/vs/workbench/contrib/testing/browser/media/testing.css +++ b/src/vs/workbench/contrib/testing/browser/media/testing.css @@ -19,10 +19,11 @@ .test-output-peek-tree .test-peek-item .name { display: flex; align-items: center; -} - -.test-explorer .test-item .label { - white-space: pre-wrap; + flex-grow: 1; + gap: 0.15em; + width: 0; + overflow: hidden; + white-space: nowrap; } .test-explorer .test-item, From 14a015aef6484635c13127a1dd82109991ec2600 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Thu, 30 Jun 2022 11:48:38 -0700 Subject: [PATCH 105/150] Store authentication session ID as machine specific data (#153846) Store authentication session ID as machine specific --- .../browser/sessionSyncWorkbenchService.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/services/sessionSync/browser/sessionSyncWorkbenchService.ts b/src/vs/workbench/services/sessionSync/browser/sessionSyncWorkbenchService.ts index aea04417075..2c0a8bb167e 100644 --- a/src/vs/workbench/services/sessionSync/browser/sessionSyncWorkbenchService.ts +++ b/src/vs/workbench/services/sessionSync/browser/sessionSyncWorkbenchService.ts @@ -294,7 +294,7 @@ export class SessionSyncWorkbenchService extends Disposable implements ISessionS if (sessionId === undefined) { this.storageService.remove(SessionSyncWorkbenchService.CACHED_SESSION_STORAGE_KEY, StorageScope.APPLICATION); } else { - this.storageService.store(SessionSyncWorkbenchService.CACHED_SESSION_STORAGE_KEY, sessionId, StorageScope.APPLICATION, StorageTarget.USER); + this.storageService.store(SessionSyncWorkbenchService.CACHED_SESSION_STORAGE_KEY, sessionId, StorageScope.APPLICATION, StorageTarget.MACHINE); } } @@ -306,10 +306,15 @@ export class SessionSyncWorkbenchService extends Disposable implements ISessionS private async onDidChangeStorage(e: IStorageValueChangeEvent): Promise { if (e.key === SessionSyncWorkbenchService.CACHED_SESSION_STORAGE_KEY && e.scope === StorageScope.APPLICATION - && this.#authenticationInfo?.sessionId !== this.existingSessionId ) { - this.#authenticationInfo = undefined; - this.initialized = false; + const newSessionId = this.existingSessionId; + const previousSessionId = this.#authenticationInfo?.sessionId; + + if (previousSessionId !== newSessionId) { + this.logService.trace(`Edit Sessions: resetting authentication state because authentication session ID preference changed from ${previousSessionId} to ${newSessionId}.`); + this.#authenticationInfo = undefined; + this.initialized = false; + } } } From 88ffbc7a7a81a926c7969ad7428e2ffc54370f42 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Thu, 30 Jun 2022 15:54:08 -0400 Subject: [PATCH 106/150] add `task.showDecorations` setting (#153851) fix #153812 --- .../tasks/browser/task.contribution.ts | 5 ++++ .../tasks/browser/terminalTaskSystem.ts | 30 +++++++++++-------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts index c9bdcd49adb..eff66ddba5f 100644 --- a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts @@ -496,6 +496,11 @@ configurationRegistry.registerConfiguration({ description: nls.localize('task.quickOpen.showAll', "Causes the Tasks: Run Task command to use the slower \"show all\" behavior instead of the faster two level picker where tasks are grouped by provider."), default: false }, + 'task.showDecorations': { + type: 'boolean', + description: nls.localize('task.showDecorations', "Shows decorations at points of interest in the terminal buffer such as the first problem found via a watch task. Note that this will only take effect for future tasks."), + default: true + }, 'task.saveBeforeRun': { markdownDescription: nls.localize( 'task.saveBeforeRun', diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index 9b1b4aa4346..d1dd693f370 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -56,9 +56,6 @@ import { GroupKind } from 'vs/workbench/contrib/tasks/common/taskConfiguration'; import { Codicon } from 'vs/base/common/codicons'; import { VSCodeOscProperty, VSCodeOscPt, VSCodeSequence } from 'vs/workbench/contrib/terminal/browser/terminalEscapeSequences'; -const taskShellIntegrationStartSequence = VSCodeSequence(VSCodeOscPt.PromptStart) + VSCodeSequence(VSCodeOscPt.Property, `${VSCodeOscProperty.Task}=True`) + VSCodeSequence(VSCodeOscPt.CommandStart); -const taskShellIntegrationOutputSequence = VSCodeSequence(VSCodeOscPt.CommandExecuted); - interface ITerminalData { terminal: ITerminalInstance; lastTask: string; @@ -211,6 +208,13 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { private readonly _onDidStateChange: Emitter; + get taskShellIntegrationStartSequence(): string { + return this._configurationService.getValue('task.showDecorations') ? VSCodeSequence(VSCodeOscPt.PromptStart) + VSCodeSequence(VSCodeOscPt.Property, `${VSCodeOscProperty.Task}=True`) + VSCodeSequence(VSCodeOscPt.CommandStart) : ''; + } + get taskShellIntegrationOutputSequence(): string { + return this._configurationService.getValue('task.showDecorations') ? VSCodeSequence(VSCodeOscPt.CommandExecuted) : ''; + } + constructor( private _terminalService: ITerminalService, private _terminalGroupService: ITerminalGroupService, @@ -1132,18 +1136,18 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { shellLaunchConfig.args = windowsShellArgs ? combinedShellArgs.join(' ') : combinedShellArgs; if (task.command.presentation && task.command.presentation.echo) { if (needsFolderQualification && workspaceFolder) { - shellLaunchConfig.initialText = taskShellIntegrationStartSequence + formatMessageForTerminal(nls.localize({ + shellLaunchConfig.initialText = this.taskShellIntegrationStartSequence + formatMessageForTerminal(nls.localize({ key: 'task.executingInFolder', comment: ['The workspace folder the task is running in', 'The task command line or label'] - }, 'Executing task in folder {0}: {1}', workspaceFolder.name, commandLine), { excludeLeadingNewLine: true }) + taskShellIntegrationOutputSequence; + }, 'Executing task in folder {0}: {1}', workspaceFolder.name, commandLine), { excludeLeadingNewLine: true }) + this.taskShellIntegrationOutputSequence; } else { - shellLaunchConfig.initialText = taskShellIntegrationStartSequence + formatMessageForTerminal(nls.localize({ + shellLaunchConfig.initialText = this.taskShellIntegrationStartSequence + formatMessageForTerminal(nls.localize({ key: 'task.executing', comment: ['The task command line or label'] - }, 'Executing task: {0}', commandLine), { excludeLeadingNewLine: true }) + taskShellIntegrationOutputSequence; + }, 'Executing task: {0}', commandLine), { excludeLeadingNewLine: true }) + this.taskShellIntegrationOutputSequence; } } else { - shellLaunchConfig.initialText = taskShellIntegrationStartSequence + taskShellIntegrationOutputSequence; + shellLaunchConfig.initialText = this.taskShellIntegrationStartSequence + this.taskShellIntegrationOutputSequence; } } else { const commandExecutable = (task.command.runtime !== RuntimeType.CustomExecution) ? CommandString.value(command) : undefined; @@ -1172,18 +1176,18 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { return args.join(' '); }; if (needsFolderQualification && workspaceFolder) { - shellLaunchConfig.initialText = taskShellIntegrationStartSequence + formatMessageForTerminal(nls.localize({ + shellLaunchConfig.initialText = this.taskShellIntegrationStartSequence + formatMessageForTerminal(nls.localize({ key: 'task.executingInFolder', comment: ['The workspace folder the task is running in', 'The task command line or label'] - }, 'Executing task in folder {0}: {1}', workspaceFolder.name, `${shellLaunchConfig.executable} ${getArgsToEcho(shellLaunchConfig.args)}`), { excludeLeadingNewLine: true }) + taskShellIntegrationOutputSequence; + }, 'Executing task in folder {0}: {1}', workspaceFolder.name, `${shellLaunchConfig.executable} ${getArgsToEcho(shellLaunchConfig.args)}`), { excludeLeadingNewLine: true }) + this.taskShellIntegrationOutputSequence; } else { - shellLaunchConfig.initialText = taskShellIntegrationStartSequence + formatMessageForTerminal(nls.localize({ + shellLaunchConfig.initialText = this.taskShellIntegrationStartSequence + formatMessageForTerminal(nls.localize({ key: 'task.executing', comment: ['The task command line or label'] - }, 'Executing task: {0}', `${shellLaunchConfig.executable} ${getArgsToEcho(shellLaunchConfig.args)}`), { excludeLeadingNewLine: true }) + taskShellIntegrationOutputSequence; + }, 'Executing task: {0}', `${shellLaunchConfig.executable} ${getArgsToEcho(shellLaunchConfig.args)}`), { excludeLeadingNewLine: true }) + this.taskShellIntegrationOutputSequence; } } else { - shellLaunchConfig.initialText = taskShellIntegrationStartSequence + taskShellIntegrationOutputSequence; + shellLaunchConfig.initialText = this.taskShellIntegrationStartSequence + this.taskShellIntegrationOutputSequence; } } From e777525b335574013467199121c79d98f012c39c Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Thu, 30 Jun 2022 22:19:00 +0200 Subject: [PATCH 107/150] Allow quotes in links if the link didn't begin with quotes (#153857) Fixes #151631: Allow quotes in links if the link didn't begin with quotes --- src/vs/editor/common/languages/linkComputer.ts | 8 ++++---- src/vs/editor/test/common/modes/linkComputer.test.ts | 7 +++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/common/languages/linkComputer.ts b/src/vs/editor/common/languages/linkComputer.ts index 0ee39a20229..e7111b3ba3d 100644 --- a/src/vs/editor/common/languages/linkComputer.ts +++ b/src/vs/editor/common/languages/linkComputer.ts @@ -258,15 +258,15 @@ export class LinkComputer { case CharCode.CloseCurlyBrace: chClass = (hasOpenCurlyBracket ? CharacterClass.None : CharacterClass.ForceTermination); break; - /* The following three rules make it that ' or " or ` are allowed inside links if the link began with a different one */ + /* The following three rules make it that ' or " or ` are allowed inside links if the link didn't begin with them */ case CharCode.SingleQuote: - chClass = (linkBeginChCode === CharCode.DoubleQuote || linkBeginChCode === CharCode.BackTick) ? CharacterClass.None : CharacterClass.ForceTermination; + chClass = (linkBeginChCode === CharCode.SingleQuote ? CharacterClass.ForceTermination : CharacterClass.None); break; case CharCode.DoubleQuote: - chClass = (linkBeginChCode === CharCode.SingleQuote || linkBeginChCode === CharCode.BackTick) ? CharacterClass.None : CharacterClass.ForceTermination; + chClass = (linkBeginChCode === CharCode.DoubleQuote ? CharacterClass.ForceTermination : CharacterClass.None); break; case CharCode.BackTick: - chClass = (linkBeginChCode === CharCode.SingleQuote || linkBeginChCode === CharCode.DoubleQuote) ? CharacterClass.None : CharacterClass.ForceTermination; + chClass = (linkBeginChCode === CharCode.BackTick ? CharacterClass.ForceTermination : CharacterClass.None); break; case CharCode.Asterisk: // `*` terminates a link if the link began with `*` diff --git a/src/vs/editor/test/common/modes/linkComputer.test.ts b/src/vs/editor/test/common/modes/linkComputer.test.ts index 8fc6d1e76c0..4774a9ec930 100644 --- a/src/vs/editor/test/common/modes/linkComputer.test.ts +++ b/src/vs/editor/test/common/modes/linkComputer.test.ts @@ -258,4 +258,11 @@ suite('Editor Modes - Link Computer', () => { 'https://site.web/page.html ' ); }); + + test('issue #151631: Link parsing stoped where comments include a single quote ', () => { + assertLink( + `aa https://regexper.com/#%2F''%2F aa`, + ` https://regexper.com/#%2F''%2F `, + ); + }); }); From a449148a14dcc5c841d113ed74d4bbfcb9f80643 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Fri, 1 Jul 2022 00:26:59 +0200 Subject: [PATCH 108/150] Keep block cursor as wide as a typical character when it sits on a tab (#153859) Fixes #151959: Keep block cursor as wide as a typical character when it sits on a tab --- src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts b/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts index 5d09d9920fa..d362663580b 100644 --- a/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts +++ b/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts @@ -172,7 +172,13 @@ export class ViewCursor { } const range = firstVisibleRangeForCharacter.ranges[0]; - const width = range.width < 1 ? this._typicalHalfwidthCharacterWidth : range.width; + const width = ( + nextGrapheme === '\t' + ? this._typicalHalfwidthCharacterWidth + : (range.width < 1 + ? this._typicalHalfwidthCharacterWidth + : range.width) + ); let textContentClassName = ''; if (this._cursorStyle === TextEditorCursorStyle.Block) { From bbce24d8bbecaf45182f8fd09adeaaeece57c958 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 30 Jun 2022 15:32:32 -0700 Subject: [PATCH 109/150] Handle fs error in markdown path completions (#153869) Fixes #153867 --- .../src/languageFeatures/pathCompletions.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/extensions/markdown-language-features/src/languageFeatures/pathCompletions.ts b/extensions/markdown-language-features/src/languageFeatures/pathCompletions.ts index 448da308a25..28f75b57444 100644 --- a/extensions/markdown-language-features/src/languageFeatures/pathCompletions.ts +++ b/extensions/markdown-language-features/src/languageFeatures/pathCompletions.ts @@ -287,7 +287,13 @@ export class MdVsCodePathCompletionProvider implements vscode.CompletionItemProv const pathSegmentEnd = position.translate({ characterDelta: context.linkSuffix.length }); const replacementRange = new vscode.Range(pathSegmentStart, pathSegmentEnd); - const dirInfo = await this.workspace.readDirectory(parentDir); + let dirInfo: [string, vscode.FileType][]; + try { + dirInfo = await this.workspace.readDirectory(parentDir); + } catch { + return; + } + for (const [name, type] of dirInfo) { // Exclude paths that start with `.` if (name.startsWith('.')) { From a8937353c699dab64012a9d527b2ea44f4a7f4fa Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Thu, 30 Jun 2022 15:39:08 -0700 Subject: [PATCH 110/150] Show both localized and original edit sessions command names in palette (#153870) --- .../contrib/sessionSync/browser/sessionSync.contribution.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts b/src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts index 6aa03f41d3c..d075977cb45 100644 --- a/src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts +++ b/src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts @@ -44,15 +44,15 @@ registerSingleton(ISessionSyncWorkbenchService, SessionSyncWorkbenchService); const resumeLatestCommand = { id: 'workbench.experimental.editSessions.actions.resumeLatest', - title: localize('resume latest', "{0}: Resume Latest Edit Session", EDIT_SESSION_SYNC_TITLE), + title: { value: localize('resume latest', "{0}: Resume Latest Edit Session", EDIT_SESSION_SYNC_TITLE), original: 'Edit Sessions' }, }; const storeCurrentCommand = { id: 'workbench.experimental.editSessions.actions.storeCurrent', - title: localize('store current', "{0}: Store Current Edit Session", EDIT_SESSION_SYNC_TITLE), + title: { value: localize('store current', "{0}: Store Current Edit Session", EDIT_SESSION_SYNC_TITLE), original: 'Edit Sessions' }, }; const continueEditSessionCommand = { id: '_workbench.experimental.editSessions.actions.continueEditSession', - title: localize('continue edit session', "Continue Edit Session..."), + title: { value: localize('continue edit session', "Continue Edit Session..."), original: 'Continue Edit Session...' }, }; const openLocalFolderCommand = { id: '_workbench.experimental.editSessions.actions.continueEditSession.openLocalFolder', From e44361365e0a477df6761b9ae0d975b0cbd96a37 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 30 Jun 2022 15:43:31 -0700 Subject: [PATCH 111/150] Don't include reference links that are inside other links (#153864) Fixes #150921 --- .../src/languageFeatures/diagnostics.ts | 12 +- .../src/languageFeatures/documentLinks.ts | 234 ++++++++++-------- .../src/languageFeatures/references.ts | 2 +- .../src/languageFeatures/rename.ts | 2 +- .../src/test/documentLinkProvider.test.ts | 40 +-- 5 files changed, 171 insertions(+), 119 deletions(-) diff --git a/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts b/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts index bb54aabc101..f403b8ec7d6 100644 --- a/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts +++ b/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts @@ -514,17 +514,17 @@ export class DiagnosticComputer { const diagnostics: vscode.Diagnostic[] = []; for (const link of links) { if (link.href.kind === 'internal' - && link.source.text.startsWith('#') + && link.source.hrefText.startsWith('#') && link.href.path.toString() === doc.uri.toString() && link.href.fragment && !toc.lookup(link.href.fragment) ) { - if (!this.isIgnoredLink(options, link.source.text)) { + if (!this.isIgnoredLink(options, link.source.hrefText)) { diagnostics.push(new LinkDoesNotExistDiagnostic( link.source.hrefRange, localize('invalidHeaderLink', 'No header found: \'{0}\'', link.href.fragment), severity, - link.source.text)); + link.source.hrefText)); } } } @@ -556,7 +556,7 @@ export class DiagnosticComputer { const fragmentErrorSeverity = toSeverity(typeof options.validateMarkdownFileLinkFragments === 'undefined' ? options.validateFragmentLinks : options.validateMarkdownFileLinkFragments); // We've already validated our own fragment links in `validateOwnHeaderLinks` - const linkSet = new FileLinkMap(links.filter(link => !link.source.text.startsWith('#'))); + const linkSet = new FileLinkMap(links.filter(link => !link.source.hrefText.startsWith('#'))); if (linkSet.size === 0) { return []; } @@ -585,10 +585,10 @@ export class DiagnosticComputer { if (fragmentLinks.length) { const toc = await this.tocProvider.get(resolvedHrefPath); for (const link of fragmentLinks) { - if (!toc.lookup(link.fragment) && !this.isIgnoredLink(options, link.source.pathText) && !this.isIgnoredLink(options, link.source.text)) { + if (!toc.lookup(link.fragment) && !this.isIgnoredLink(options, link.source.pathText) && !this.isIgnoredLink(options, link.source.hrefText)) { const msg = localize('invalidLinkToHeaderInOtherFile', 'Header does not exist in file: {0}', link.fragment); const range = link.source.fragmentRange?.with({ start: link.source.fragmentRange.start.translate(0, -1) }) ?? link.source.hrefRange; - diagnostics.push(new LinkDoesNotExistDiagnostic(range, msg, fragmentErrorSeverity, link.source.text)); + diagnostics.push(new LinkDoesNotExistDiagnostic(range, msg, fragmentErrorSeverity, link.source.hrefText)); } } } diff --git a/extensions/markdown-language-features/src/languageFeatures/documentLinks.ts b/extensions/markdown-language-features/src/languageFeatures/documentLinks.ts index 8c322ce9756..4fe6320c6cc 100644 --- a/extensions/markdown-language-features/src/languageFeatures/documentLinks.ts +++ b/extensions/markdown-language-features/src/languageFeatures/documentLinks.ts @@ -108,18 +108,34 @@ function getWorkspaceFolder(document: ITextDocument) { } export interface MdLinkSource { + /** + * The full range of the link. + */ + readonly range: vscode.Range; + + /** + * The file where the link is defined. + */ + readonly resource: vscode.Uri; + /** * The original text of the link destination in code. */ - readonly text: string; + readonly hrefText: string; /** * The original text of just the link's path in code. */ readonly pathText: string; - readonly resource: vscode.Uri; + /** + * The range of the path. + */ readonly hrefRange: vscode.Range; + + /** + * The range of the fragment within the path. + */ readonly fragmentRange: vscode.Range | undefined; } @@ -145,32 +161,37 @@ function extractDocumentLink( document: ITextDocument, pre: string, rawLink: string, - matchIndex: number | undefined + matchIndex: number, + fullMatch: string, ): MdLink | undefined { const isAngleBracketLink = rawLink.startsWith('<'); const link = stripAngleBrackets(rawLink); - const offset = (matchIndex || 0) + pre.length + (isAngleBracketLink ? 1 : 0); - const linkStart = document.positionAt(offset); - const linkEnd = document.positionAt(offset + link.length); + let linkTarget: ExternalHref | InternalHref | undefined; try { - const linkTarget = resolveLink(document, link); - if (!linkTarget) { - return undefined; - } - return { - kind: 'link', - href: linkTarget, - source: { - text: link, - resource: document.uri, - hrefRange: new vscode.Range(linkStart, linkEnd), - ...getLinkSourceFragmentInfo(document, link, linkStart, linkEnd), - } - }; + linkTarget = resolveLink(document, link); } catch { return undefined; } + if (!linkTarget) { + return undefined; + } + + const linkStart = document.positionAt(matchIndex); + const linkEnd = linkStart.translate(0, fullMatch.length); + const hrefStart = linkStart.translate(0, pre.length + (isAngleBracketLink ? 1 : 0)); + const hrefEnd = hrefStart.translate(0, link.length); + return { + kind: 'link', + href: linkTarget, + source: { + hrefText: link, + resource: document.uri, + range: new vscode.Range(linkStart, linkEnd), + hrefRange: new vscode.Range(hrefStart, hrefEnd), + ...getLinkSourceFragmentInfo(document, link, hrefStart, hrefEnd), + } + }; } function getFragmentRange(text: string, start: vscode.Position, end: vscode.Position): vscode.Range | undefined { @@ -278,13 +299,28 @@ class NoLinkRanges { /** * Inline code spans where links should not be detected */ - public readonly inline: Map + public readonly inline: Map ) { } contains(position: vscode.Position): boolean { return this.multiline.some(interval => position.line >= interval[0] && position.line < interval[1]) || !!this.inline.get(position.line)?.some(inlineRange => inlineRange.contains(position)); } + + concatInline(inlineRanges: Iterable): NoLinkRanges { + const newInline = new Map(this.inline); + for (const range of inlineRanges) { + for (let line = range.start.line; line <= range.end.line; ++line) { + let entry = newInline.get(line); + if (!entry) { + entry = []; + newInline.set(line, entry); + } + entry.push(range); + } + } + return new NoLinkRanges(this.multiline, newInline); + } } /** @@ -302,9 +338,10 @@ export class MdLinkComputer { return []; } + const inlineLinks = Array.from(this.getInlineLinks(document, noLinkRanges)); return Array.from([ - ...this.getInlineLinks(document, noLinkRanges), - ...this.getReferenceLinks(document, noLinkRanges), + ...inlineLinks, + ...this.getReferenceLinks(document, noLinkRanges.concatInline(inlineLinks.map(x => x.source.range))), ...this.getLinkDefinitions(document, noLinkRanges), ...this.getAutoLinks(document, noLinkRanges), ]); @@ -313,13 +350,13 @@ export class MdLinkComputer { private *getInlineLinks(document: ITextDocument, noLinkRanges: NoLinkRanges): Iterable { const text = document.getText(); for (const match of text.matchAll(linkPattern)) { - const matchLinkData = extractDocumentLink(document, match[1], match[2], match.index); + const matchLinkData = extractDocumentLink(document, match[1], match[2], match.index ?? 0, match[0]); if (matchLinkData && !noLinkRanges.contains(matchLinkData.source.hrefRange.start)) { yield matchLinkData; // Also check link destination for links for (const innerMatch of match[1].matchAll(linkPattern)) { - const innerData = extractDocumentLink(document, innerMatch[1], innerMatch[2], (match.index ?? 0) + (innerMatch.index ?? 0)); + const innerData = extractDocumentLink(document, innerMatch[1], innerMatch[2], (match.index ?? 0) + (innerMatch.index ?? 0), innerMatch[0]); if (innerData) { yield innerData; } @@ -328,77 +365,83 @@ export class MdLinkComputer { } } - private * getAutoLinks(document: ITextDocument, noLinkRanges: NoLinkRanges): Iterable { + private *getAutoLinks(document: ITextDocument, noLinkRanges: NoLinkRanges): Iterable { const text = document.getText(); - for (const match of text.matchAll(autoLinkPattern)) { + const linkOffset = (match.index ?? 0); + const linkStart = document.positionAt(linkOffset); + if (noLinkRanges.contains(linkStart)) { + continue; + } + const link = match[1]; const linkTarget = resolveLink(document, link); - if (linkTarget) { - const offset = (match.index ?? 0) + 1; - const linkStart = document.positionAt(offset); - const linkEnd = document.positionAt(offset + link.length); - const hrefRange = new vscode.Range(linkStart, linkEnd); - if (noLinkRanges.contains(hrefRange.start)) { - continue; - } - yield { - kind: 'link', - href: linkTarget, - source: { - text: link, - resource: document.uri, - hrefRange: new vscode.Range(linkStart, linkEnd), - ...getLinkSourceFragmentInfo(document, link, linkStart, linkEnd), - } - }; + if (!linkTarget) { + continue; } + + const linkEnd = linkStart.translate(0, match[0].length); + const hrefStart = linkStart.translate(0, 1); + const hrefEnd = hrefStart.translate(0, link.length); + yield { + kind: 'link', + href: linkTarget, + source: { + hrefText: link, + resource: document.uri, + hrefRange: new vscode.Range(hrefStart, hrefEnd), + range: new vscode.Range(linkStart, linkEnd), + ...getLinkSourceFragmentInfo(document, link, hrefStart, hrefEnd), + } + }; } } private *getReferenceLinks(document: ITextDocument, noLinkRanges: NoLinkRanges): Iterable { const text = document.getText(); for (const match of text.matchAll(referenceLinkPattern)) { - let linkStart: vscode.Position; - let linkEnd: vscode.Position; + const linkStart = document.positionAt(match.index ?? 0); + if (noLinkRanges.contains(linkStart)) { + continue; + } + + let hrefStart: vscode.Position; + let hrefEnd: vscode.Position; let reference = match[4]; if (reference === '') { // [ref][], reference = match[3]; const offset = ((match.index ?? 0) + match[1].length) + 1; - linkStart = document.positionAt(offset); - linkEnd = document.positionAt(offset + reference.length); + hrefStart = document.positionAt(offset); + hrefEnd = document.positionAt(offset + reference.length); } else if (reference) { // [text][ref] const pre = match[2]; const offset = ((match.index ?? 0) + match[1].length) + pre.length; - linkStart = document.positionAt(offset); - linkEnd = document.positionAt(offset + reference.length); + hrefStart = document.positionAt(offset); + hrefEnd = document.positionAt(offset + reference.length); } else if (match[5]) { // [ref] reference = match[5]; const offset = ((match.index ?? 0) + match[1].length) + 1; - linkStart = document.positionAt(offset); - const line = document.lineAt(linkStart.line); + hrefStart = document.positionAt(offset); + const line = document.lineAt(hrefStart.line); // See if link looks like a checkbox const checkboxMatch = line.text.match(/^\s*[\-\*]\s*\[x\]/i); - if (checkboxMatch && linkStart.character <= checkboxMatch[0].length) { + if (checkboxMatch && hrefStart.character <= checkboxMatch[0].length) { continue; } - linkEnd = document.positionAt(offset + reference.length); + hrefEnd = document.positionAt(offset + reference.length); } else { continue; } - const hrefRange = new vscode.Range(linkStart, linkEnd); - if (noLinkRanges.contains(hrefRange.start)) { - continue; - } - + const linkEnd = linkStart.translate(0, match[0].length); yield { kind: 'link', source: { - text: reference, + hrefText: reference, pathText: reference, resource: document.uri, - hrefRange, + range: new vscode.Range(linkStart, linkEnd), + hrefRange: new vscode.Range(hrefStart, hrefEnd), fragmentRange: undefined, }, href: { @@ -412,44 +455,41 @@ export class MdLinkComputer { private *getLinkDefinitions(document: ITextDocument, noLinkRanges: NoLinkRanges): Iterable { const text = document.getText(); for (const match of text.matchAll(definitionPattern)) { - const pre = match[1]; - const reference = match[2]; - const link = match[3].trim(); - const offset = (match.index || 0) + pre.length; - - const refStart = document.positionAt((match.index ?? 0) + 1); - const refRange = new vscode.Range(refStart, refStart.translate({ characterDelta: reference.length })); - - let linkStart: vscode.Position; - let linkEnd: vscode.Position; - let text: string; - if (angleBracketLinkRe.test(link)) { - linkStart = document.positionAt(offset + 1); - linkEnd = document.positionAt(offset + link.length - 1); - text = link.substring(1, link.length - 1); - } else { - linkStart = document.positionAt(offset); - linkEnd = document.positionAt(offset + link.length); - text = link; - } - const hrefRange = new vscode.Range(linkStart, linkEnd); - if (noLinkRanges.contains(hrefRange.start)) { + const offset = (match.index ?? 0); + const linkStart = document.positionAt(offset); + if (noLinkRanges.contains(linkStart)) { continue; } - const target = resolveLink(document, text); - if (target) { - yield { - kind: 'definition', - source: { - text: link, - resource: document.uri, - hrefRange, - ...getLinkSourceFragmentInfo(document, link, linkStart, linkEnd), - }, - ref: { text: reference, range: refRange }, - href: target, - }; + + const pre = match[1]; + const reference = match[2]; + const rawLinkText = match[3].trim(); + const target = resolveLink(document, rawLinkText); + if (!target) { + continue; } + + const isAngleBracketLink = angleBracketLinkRe.test(rawLinkText); + const linkText = stripAngleBrackets(rawLinkText); + const hrefStart = linkStart.translate(0, pre.length + (isAngleBracketLink ? 1 : 0)); + const hrefEnd = hrefStart.translate(0, linkText.length); + const hrefRange = new vscode.Range(hrefStart, hrefEnd); + + const refStart = linkStart.translate(0, 1); + const refRange = new vscode.Range(refStart, refStart.translate({ characterDelta: reference.length })); + const linkEnd = linkStart.translate(0, match[0].length); + yield { + kind: 'definition', + source: { + hrefText: linkText, + resource: document.uri, + range: new vscode.Range(linkStart, linkEnd), + hrefRange, + ...getLinkSourceFragmentInfo(document, rawLinkText, hrefStart, hrefEnd), + }, + ref: { text: reference, range: refRange }, + href: target, + }; } } } diff --git a/extensions/markdown-language-features/src/languageFeatures/references.ts b/extensions/markdown-language-features/src/languageFeatures/references.ts index b9fe0bef931..51b7e7b92a1 100644 --- a/extensions/markdown-language-features/src/languageFeatures/references.ts +++ b/extensions/markdown-language-features/src/languageFeatures/references.ts @@ -259,7 +259,7 @@ export class MdReferencesProvider extends Disposable { } // Exclude cases where the file is implicitly referencing itself - if (link.source.text.startsWith('#') && link.source.resource.fsPath === resource.fsPath) { + if (link.source.hrefText.startsWith('#') && link.source.resource.fsPath === resource.fsPath) { continue; } diff --git a/extensions/markdown-language-features/src/languageFeatures/rename.ts b/extensions/markdown-language-features/src/languageFeatures/rename.ts index 3bff38697c5..44870e33ff1 100644 --- a/extensions/markdown-language-features/src/languageFeatures/rename.ts +++ b/extensions/markdown-language-features/src/languageFeatures/rename.ts @@ -179,7 +179,7 @@ export class MdVsCodeRenameProvider extends Disposable implements vscode.RenameP if (ref.kind === 'link') { // Try to preserve style of existing links let newPath: string; - if (ref.link.source.text.startsWith('/')) { + if (ref.link.source.hrefText.startsWith('/')) { const root = resolveDocumentLink('/', ref.link.source.resource); newPath = '/' + path.relative(root.toString(true), rawNewFilePath.toString(true)); } else { diff --git a/extensions/markdown-language-features/src/test/documentLinkProvider.test.ts b/extensions/markdown-language-features/src/test/documentLinkProvider.test.ts index 17304f59868..a96967bafd2 100644 --- a/extensions/markdown-language-features/src/test/documentLinkProvider.test.ts +++ b/extensions/markdown-language-features/src/test/documentLinkProvider.test.ts @@ -15,7 +15,7 @@ import { nulLogger } from './nulLogging'; import { assertRangeEqual, joinLines, workspacePath } from './util'; -suite('Markdown: MdLinkComputer', () => { +suite.only('Markdown: MdLinkComputer', () => { function getLinksForFile(fileContents: string): Promise { const doc = new InMemoryDocument(workspacePath('x.md'), fileContents); @@ -24,11 +24,17 @@ suite('Markdown: MdLinkComputer', () => { return linkProvider.getAllLinks(doc, noopToken); } - function assertLinksEqual(actualLinks: readonly MdLink[], expectedRanges: readonly vscode.Range[]) { - assert.strictEqual(actualLinks.length, expectedRanges.length); + function assertLinksEqual(actualLinks: readonly MdLink[], expected: ReadonlyArray) { + assert.strictEqual(actualLinks.length, expected.length); for (let i = 0; i < actualLinks.length; ++i) { - assertRangeEqual(actualLinks[i].source.hrefRange, expectedRanges[i], `Range ${i} to be equal`); + const exp = expected[i]; + if ('range' in exp) { + assertRangeEqual(actualLinks[i].source.hrefRange, exp.range, `Range ${i} to be equal`); + assert.strictEqual(actualLinks[i].source.hrefText, exp.sourceText, `Source text ${i} to be equal`); + } else { + assertRangeEqual(actualLinks[i].source.hrefRange, exp, `Range ${i} to be equal`); + } } } @@ -103,17 +109,23 @@ suite('Markdown: MdLinkComputer', () => { } }); - test('Should ignore texts in brackets inside link title (#150921)', async () => { + test('Should ignore bracketed text inside link title (#150921)', async () => { { - const links = await getLinksForFile('[some [inner bracket pairs] in title]()'); + const links = await getLinksForFile('[some [inner] in title](link)'); assertLinksEqual(links, [ - new vscode.Range(0, 39, 0, 43), + new vscode.Range(0, 24, 0, 28), ]); } { - const links = await getLinksForFile('[some [inner bracket pairs] in title](link)'); + const links = await getLinksForFile('[some [inner] in title]()'); assertLinksEqual(links, [ - new vscode.Range(0, 38, 0, 42) + new vscode.Range(0, 25, 0, 29), + ]); + } + { + const links = await getLinksForFile('[some [inner with space] in title](link)'); + assertLinksEqual(links, [ + new vscode.Range(0, 35, 0, 39), ]); } }); @@ -164,8 +176,8 @@ suite('Markdown: MdLinkComputer', () => { )); assertLinksEqual(links, [ - new vscode.Range(0, 6, 0, 9), - new vscode.Range(1, 6, 1, 8), + { range: new vscode.Range(0, 6, 0, 9), sourceText: 'b c' }, + { range: new vscode.Range(1, 6, 1, 8), sourceText: 'cd' }, ]); }); @@ -175,7 +187,7 @@ suite('Markdown: MdLinkComputer', () => { )); assertLinksEqual(links, [ - new vscode.Range(0, 9, 0, 28), + { range: new vscode.Range(0, 9, 0, 28), sourceText: 'https://example.com' }, ]); }); @@ -185,8 +197,8 @@ suite('Markdown: MdLinkComputer', () => { '[ref]: https://example.com', )); assertLinksEqual(links, [ - new vscode.Range(0, 1, 0, 4), - new vscode.Range(1, 7, 1, 26), + { range: new vscode.Range(0, 1, 0, 4), sourceText: 'ref' }, + { range: new vscode.Range(1, 7, 1, 26), sourceText: 'https://example.com' }, ]); }); From 0122f8bd4ac810db68ceccf1d421721f4c99faf7 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Thu, 30 Jun 2022 15:47:36 -0700 Subject: [PATCH 112/150] Re #153850. Add logs for iw creation. (#153862) --- src/vs/workbench/api/common/extHost.api.impl.ts | 2 +- src/vs/workbench/api/common/extHostInteractive.ts | 7 ++++++- .../interactive/browser/interactive.contribution.ts | 7 +++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 24d3391bc2c..cd38535f5f6 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -183,7 +183,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostWebviewViews = rpcProtocol.set(ExtHostContext.ExtHostWebviewViews, new ExtHostWebviewViews(rpcProtocol, extHostWebviews)); const extHostTesting = rpcProtocol.set(ExtHostContext.ExtHostTesting, new ExtHostTesting(rpcProtocol, extHostCommands)); const extHostUriOpeners = rpcProtocol.set(ExtHostContext.ExtHostUriOpeners, new ExtHostUriOpeners(rpcProtocol)); - rpcProtocol.set(ExtHostContext.ExtHostInteractive, new ExtHostInteractive(rpcProtocol, extHostNotebook, extHostDocumentsAndEditors, extHostCommands)); + rpcProtocol.set(ExtHostContext.ExtHostInteractive, new ExtHostInteractive(rpcProtocol, extHostNotebook, extHostDocumentsAndEditors, extHostCommands, extHostLogService)); // Check that no named customers are missing const expected = Object.values>(ExtHostContext); diff --git a/src/vs/workbench/api/common/extHostInteractive.ts b/src/vs/workbench/api/common/extHostInteractive.ts index b492b0c9ed4..b53d846c2e1 100644 --- a/src/vs/workbench/api/common/extHostInteractive.ts +++ b/src/vs/workbench/api/common/extHostInteractive.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { URI, UriComponents } from 'vs/base/common/uri'; +import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostInteractiveShape, IMainContext } from 'vs/workbench/api/common/extHost.protocol'; import { ApiCommand, ApiCommandArgument, ApiCommandResult, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; @@ -15,7 +16,8 @@ export class ExtHostInteractive implements ExtHostInteractiveShape { mainContext: IMainContext, private _extHostNotebooks: ExtHostNotebookController, private _textDocumentsAndEditors: ExtHostDocumentsAndEditors, - private _commands: ExtHostCommands + private _commands: ExtHostCommands, + _logService: ILogService ) { const openApiCommand = new ApiCommand( 'interactive.open', @@ -28,10 +30,13 @@ export class ExtHostInteractive implements ExtHostInteractiveShape { new ApiCommandArgument('title', 'Interactive editor title', v => true, v => v) ], new ApiCommandResult<{ notebookUri: UriComponents; inputUri: UriComponents; notebookEditorId?: string }, { notebookUri: URI; inputUri: URI; notebookEditor?: NotebookEditor }>('Notebook and input URI', (v: { notebookUri: UriComponents; inputUri: UriComponents; notebookEditorId?: string }) => { + _logService.debug('[ExtHostInteractive] open iw with notebook editor id', v.notebookEditorId); if (v.notebookEditorId !== undefined) { const editor = this._extHostNotebooks.getEditorById(v.notebookEditorId); + _logService.debug('[ExtHostInteractive] notebook editor found', editor.id); return { notebookUri: URI.revive(v.notebookUri), inputUri: URI.revive(v.inputUri), notebookEditor: editor.apiEditor }; } + _logService.debug('[ExtHostInteractive] notebook editor not found, uris for the interactive document', v.notebookUri, v.inputUri); return { notebookUri: URI.revive(v.notebookUri), inputUri: URI.revive(v.inputUri) }; }) ); diff --git a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts index 8b70cab1d33..f108213e01b 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts @@ -31,6 +31,7 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { ILogService } from 'vs/platform/log/common/log'; import { Registry } from 'vs/platform/registry/common/platform'; import { contrastBorder, listInactiveSelectionBackground, registerColor, transparent } from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; @@ -344,6 +345,7 @@ registerAction2(class extends Action2 { const editorGroupService = accessor.get(IEditorGroupsService); const historyService = accessor.get(IInteractiveHistoryService); const kernelService = accessor.get(INotebookKernelService); + const logService = accessor.get(ILogService); const group = columnToEditorGroup(editorGroupService, typeof showOptions === 'number' ? showOptions : showOptions?.viewColumn); const editorOptions = { activation: EditorActivation.PRESERVE, @@ -351,9 +353,11 @@ registerAction2(class extends Action2 { }; if (resource && resource.scheme === Schemas.vscodeInteractive) { + logService.debug('Open interactive window from resource:', resource.toString()); const resourceUri = URI.revive(resource); const editors = editorService.findEditors(resourceUri).filter(id => id.editor instanceof InteractiveEditorInput && id.editor.resource?.toString() === resourceUri.toString()); if (editors.length) { + logService.debug('Find existing interactive window:', resource.toString()); const editorInput = editors[0].editor as InteractiveEditorInput; const currentGroup = editors[0].groupId; const editor = await editorService.openEditor(editorInput, editorOptions, currentGroup); @@ -384,6 +388,8 @@ registerAction2(class extends Action2 { counter++; } while (existingNotebookDocument.has(notebookUri.toString())); + logService.debug('Open new interactive window:', notebookUri.toString(), inputUri.toString()); + if (id) { const allKernels = kernelService.getMatchingKernel({ uri: notebookUri, viewType: 'interactive' }).all; const preferredKernel = allKernels.find(kernel => kernel.id === id); @@ -397,6 +403,7 @@ registerAction2(class extends Action2 { const editorPane = await editorService.openEditor(editorInput, editorOptions, group); const editorControl = editorPane?.getControl() as { notebookEditor: NotebookEditorWidget | undefined; codeEditor: CodeEditorWidget } | undefined; // Extensions must retain references to these URIs to manipulate the interactive editor + logService.debug('New interactive window opened. Notebook editor id', editorControl?.notebookEditor?.getId()); return { notebookUri, inputUri, notebookEditorId: editorControl?.notebookEditor?.getId() }; } }); From cec6f8de885caa6e7756c8afc0d5d20df14f844d Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 30 Jun 2022 15:51:25 -0700 Subject: [PATCH 113/150] Fix background markdown rendering issues (#153871) This fixes background markdown rendering quitting after the first run, and running forever on folded cells, and rendering the last hidden item of the folded range. Fixes #151252 --- .../notebook/browser/notebookEditorWidget.ts | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 10e6e03c75f..6047d713272 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -1153,22 +1153,25 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD const endTime = Date.now() + deadline.timeRemaining(); const execute = () => { - this._backgroundMarkdownRenderRunning = false; - if (this._isDisposed) { - return; - } + try { + this._backgroundMarkdownRenderRunning = true; + if (this._isDisposed) { + return; + } - if (!this.viewModel) { - return; - } + if (!this.viewModel) { + return; + } - const firstMarkupCell = this.viewModel.viewCells.find(cell => cell.cellKind === CellKind.Markup && !this._webview?.markupPreviewMapping.has(cell.id)) as MarkupCellViewModel | undefined; - if (!firstMarkupCell) { - return; - } + const firstMarkupCell = this.viewModel.viewCells.find(cell => cell.cellKind === CellKind.Markup && !this._webview?.markupPreviewMapping.has(cell.id) && !this.cellIsHidden(cell)) as MarkupCellViewModel | undefined; + if (!firstMarkupCell) { + return; + } - this._backgroundMarkdownRenderRunning = true; - this.createMarkupPreview(firstMarkupCell); + this.createMarkupPreview(firstMarkupCell); + } finally { + this._backgroundMarkdownRenderRunning = false; + } if (Date.now() < endTime) { setTimeout0(execute); @@ -2564,10 +2567,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD return; } - const modelIndex = this.viewModel.getCellIndex(cell); - const foldedRanges = this.viewModel.getHiddenRanges(); - const isVisible = !foldedRanges.some(range => modelIndex >= range.start && modelIndex < range.end); - if (!isVisible) { + if (this.cellIsHidden(cell)) { return; } @@ -2585,6 +2585,12 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD }); } + private cellIsHidden(cell: ICellViewModel): boolean { + const modelIndex = this.viewModel!.getCellIndex(cell); + const foldedRanges = this.viewModel!.getHiddenRanges(); + return foldedRanges.some(range => modelIndex >= range.start && modelIndex <= range.end); + } + async unhideMarkupPreviews(cells: readonly MarkupCellViewModel[]) { if (!this._webview) { return; From 0b3574dcef8f35fec4ee4f83dc958c1f16ef6fce Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 30 Jun 2022 16:19:49 -0700 Subject: [PATCH 114/150] Update id of markdown-it renderer (#153876) Fixes #153873 --- extensions/markdown-language-features/package.json | 2 +- extensions/markdown-math/notebook/katex.ts | 4 ++-- extensions/markdown-math/package.json | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index eb4234c0cf0..0ce010f6cc3 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -47,7 +47,7 @@ "contributes": { "notebookRenderer": [ { - "id": "markdownItRenderer", + "id": "vscode.markdown-it-renderer", "displayName": "Markdown it renderer", "entrypoint": "./notebook-out/index.js", "mimeTypes": [ diff --git a/extensions/markdown-math/notebook/katex.ts b/extensions/markdown-math/notebook/katex.ts index ee68b15d61b..299ab9373de 100644 --- a/extensions/markdown-math/notebook/katex.ts +++ b/extensions/markdown-math/notebook/katex.ts @@ -8,9 +8,9 @@ import type { RendererContext } from 'vscode-notebook-renderer'; const styleHref = import.meta.url.replace(/katex.js$/, 'katex.min.css'); export async function activate(ctx: RendererContext) { - const markdownItRenderer = (await ctx.getRenderer('markdownItRenderer')) as undefined | any; + const markdownItRenderer = (await ctx.getRenderer('vscode.markdown-it-renderer')) as undefined | any; if (!markdownItRenderer) { - throw new Error('Could not load markdownItRenderer'); + throw new Error(`Could not load 'vscode.markdown-it-renderer'`); } // Add katex styles to be copied to shadow dom diff --git a/extensions/markdown-math/package.json b/extensions/markdown-math/package.json index d3bb21a553a..b8ed2d88a74 100644 --- a/extensions/markdown-math/package.json +++ b/extensions/markdown-math/package.json @@ -58,10 +58,10 @@ ], "notebookRenderer": [ { - "id": "markdownItRenderer-katex", + "id": "vscode.markdown-it-katex-extension", "displayName": "Markdown it KaTeX renderer", "entrypoint": { - "extends": "markdownItRenderer", + "extends": "vscode.markdown-it-renderer", "path": "./notebook-out/katex.js" } } From c7c3174e136c091ace576fc1cab0bd10caa486a4 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 1 Jul 2022 08:32:10 +0200 Subject: [PATCH 115/150] Fix #153797 --- .../electron-browser/sharedProcess/contrib/extensionsCleaner.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/extensionsCleaner.ts b/src/vs/code/electron-browser/sharedProcess/contrib/extensionsCleaner.ts index e266b173425..221d77ffdfa 100644 --- a/src/vs/code/electron-browser/sharedProcess/contrib/extensionsCleaner.ts +++ b/src/vs/code/electron-browser/sharedProcess/contrib/extensionsCleaner.ts @@ -34,7 +34,7 @@ export class ExtensionsCleaner extends Disposable { ) { super(); - extensionManagementService.removeUninstalledExtensions(this.userDataProfilesService.profiles.length > 1); + extensionManagementService.removeUninstalledExtensions(this.userDataProfilesService.profiles.length === 0); migrateUnsupportedExtensions(extensionManagementService, extensionGalleryService, extensionStorageService, extensionEnablementService, logService); ExtensionStorageService.removeOutdatedExtensionVersions(extensionManagementService, storageService); this._register(instantiationService.createInstance(ProfileExtensionsCleaner)); From 53c752ac8a9ce36b3ae397a079a5c518bb4deec9 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 1 Jul 2022 11:56:29 +0200 Subject: [PATCH 116/150] Git - Disable commit button while merge is in progress (#153890) Disable commit button while merge is in progress --- extensions/git/src/actionButton.ts | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/extensions/git/src/actionButton.ts b/extensions/git/src/actionButton.ts index 7fa03831f2f..2eabbeaa9e6 100644 --- a/extensions/git/src/actionButton.ts +++ b/extensions/git/src/actionButton.ts @@ -14,6 +14,7 @@ const localize = nls.loadMessageBundle(); interface ActionButtonState { readonly HEAD: Branch | undefined; readonly isCommitInProgress: boolean; + readonly isMergeInProgress: boolean; readonly isSyncInProgress: boolean; readonly repositoryHasChanges: boolean; } @@ -34,7 +35,13 @@ export class ActionButtonCommand { private disposables: Disposable[] = []; constructor(readonly repository: Repository) { - this._state = { HEAD: undefined, isCommitInProgress: false, isSyncInProgress: false, repositoryHasChanges: false }; + this._state = { + HEAD: undefined, + isCommitInProgress: false, + isMergeInProgress: false, + isSyncInProgress: false, + repositoryHasChanges: false + }; repository.onDidRunGitStatus(this.onDidRunGitStatus, this, this.disposables); repository.onDidChangeOperations(this.onDidChangeOperations, this, this.disposables); @@ -123,7 +130,7 @@ export class ActionButtonCommand { }, ] ], - enabled: this.state.repositoryHasChanges && !this.state.isCommitInProgress + enabled: this.state.repositoryHasChanges && !this.state.isCommitInProgress && !this.state.isMergeInProgress }; } @@ -131,8 +138,8 @@ export class ActionButtonCommand { const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); const showActionButton = config.get<{ publish: boolean }>('showActionButton', { publish: true }); - // Branch does have an upstream, commit is in progress, or the button is disabled - if (this.state.HEAD?.upstream || this.state.isCommitInProgress || !showActionButton.publish) { return undefined; } + // Branch does have an upstream, commit/merge is in progress, or the button is disabled + if (this.state.HEAD?.upstream || this.state.isCommitInProgress || this.state.isMergeInProgress || !showActionButton.publish) { return undefined; } return { command: { @@ -151,8 +158,8 @@ export class ActionButtonCommand { const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); const showActionButton = config.get<{ sync: boolean }>('showActionButton', { sync: true }); - // Branch does not have an upstream, commit is in progress, or the button is disabled - if (!this.state.HEAD?.upstream || this.state.isCommitInProgress || !showActionButton.sync) { return undefined; } + // Branch does not have an upstream, commit/merge is in progress, or the button is disabled + if (!this.state.HEAD?.upstream || this.state.isCommitInProgress || this.state.isMergeInProgress || !showActionButton.sync) { return undefined; } const ahead = this.state.HEAD.ahead ? ` ${this.state.HEAD.ahead}$(arrow-up)` : ''; const behind = this.state.HEAD.behind ? ` ${this.state.HEAD.behind}$(arrow-down)` : ''; @@ -190,9 +197,10 @@ export class ActionButtonCommand { this.state = { ...this.state, HEAD: this.repository.HEAD, + isMergeInProgress: + this.repository.mergeGroup.resourceStates.length !== 0, repositoryHasChanges: this.repository.indexGroup.resourceStates.length !== 0 || - this.repository.mergeGroup.resourceStates.length !== 0 || this.repository.untrackedGroup.resourceStates.length !== 0 || this.repository.workingTreeGroup.resourceStates.length !== 0 }; From 48fef0c1da52ea1c603355cbf97e722455460e7b Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Fri, 1 Jul 2022 15:04:12 +0200 Subject: [PATCH 117/150] Share link from File Menu shouldn't include active file (#153911) Fixes #153537 --- extensions/github/package.json | 10 +++++++++- extensions/github/src/commands.ts | 24 ++++++++++++++++-------- extensions/github/src/links.ts | 12 +++++------- 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/extensions/github/package.json b/extensions/github/package.json index ed50579d22c..fb33fbaf3a5 100644 --- a/extensions/github/package.json +++ b/extensions/github/package.json @@ -37,6 +37,10 @@ { "command": "github.copyVscodeDevLink", "title": "Copy vscode.dev Link" + }, + { + "command": "github.copyVscodeDevLinkFile", + "title": "Copy vscode.dev Link" } ], "menus": { @@ -48,11 +52,15 @@ { "command": "github.copyVscodeDevLink", "when": "false" + }, + { + "command": "github.copyVscodeDevLinkFile", + "when": "false" } ], "file/share": [ { - "command": "github.copyVscodeDevLink", + "command": "github.copyVscodeDevLinkFile", "when": "github.hasGitHubRepo" } ], diff --git a/extensions/github/src/commands.ts b/extensions/github/src/commands.ts index d2d4fb814f7..8c68f36bfc6 100644 --- a/extensions/github/src/commands.ts +++ b/extensions/github/src/commands.ts @@ -9,6 +9,17 @@ import { publishRepository } from './publish'; import { DisposableStore } from './util'; import { getPermalink } from './links'; +async function copyVscodeDevLink(gitAPI: GitAPI, useSelection: boolean) { + try { + const permalink = getPermalink(gitAPI, useSelection, 'https://vscode.dev/github'); + if (permalink) { + return vscode.env.clipboard.writeText(permalink); + } + } catch (err) { + vscode.window.showErrorMessage(err.message); + } +} + export function registerCommands(gitAPI: GitAPI): vscode.Disposable { const disposables = new DisposableStore(); @@ -21,14 +32,11 @@ export function registerCommands(gitAPI: GitAPI): vscode.Disposable { })); disposables.add(vscode.commands.registerCommand('github.copyVscodeDevLink', async () => { - try { - const permalink = getPermalink(gitAPI, 'https://vscode.dev/github'); - if (permalink) { - vscode.env.clipboard.writeText(permalink); - } - } catch (err) { - vscode.window.showErrorMessage(err.message); - } + return copyVscodeDevLink(gitAPI, true); + })); + + disposables.add(vscode.commands.registerCommand('github.copyVscodeDevLinkFile', async () => { + return copyVscodeDevLink(gitAPI, false); })); return disposables; diff --git a/extensions/github/src/links.ts b/extensions/github/src/links.ts index 3b0bc3ce8aa..a025c79a804 100644 --- a/extensions/github/src/links.ts +++ b/extensions/github/src/links.ts @@ -43,13 +43,11 @@ function rangeString(range: vscode.Range | undefined) { return hash; } -export function getPermalink(gitAPI: GitAPI, hostPrefix?: string): string | undefined { +export function getPermalink(gitAPI: GitAPI, useSelection: boolean, hostPrefix?: string): string | undefined { hostPrefix = hostPrefix ?? 'https://github.com'; const { uri, range } = getFileAndPosition(); - if (!uri) { - return; - } - const gitRepo = getRepositoryForFile(gitAPI, uri); + // Use the first repo if we cannot determine a repo from the uri. + const gitRepo = (uri ? getRepositoryForFile(gitAPI, uri) : gitAPI.repositories[0]) ?? gitAPI.repositories[0]; if (!gitRepo) { return; } @@ -71,8 +69,8 @@ export function getPermalink(gitAPI: GitAPI, hostPrefix?: string): string | unde } const commitHash = gitRepo.state.HEAD?.commit; - const pathSegment = uri.path.substring(gitRepo.rootUri.path.length); + const fileSegments = (useSelection && uri) ? `${uri.path.substring(gitRepo.rootUri.path.length)}${rangeString(range)}` : ''; return `${hostPrefix}/${repo.owner}/${repo.repo}/blob/${commitHash - }${pathSegment}${rangeString(range)}`; + }${fileSegments}`; } From fb97ffc1609dca6186eb6532095b39bde9938658 Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 1 Jul 2022 15:39:51 +0200 Subject: [PATCH 118/150] be tolerant to other events happing but still enforce that the expected events are there fixes https://github.com/microsoft/vscode/issues/153288 --- .../src/singlefolder-tests/workspace.test.ts | 37 ++++++++----------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts index a055cf45520..6e088ce9ee5 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts @@ -286,9 +286,6 @@ suite('vscode API - workspace', () => { sub.dispose(); }); - function assertEqualPath(a: string, b: string): void { - assert.ok(pathEquals(a, b), `${a} <-> ${b}`); - } test('events: onDidOpenTextDocument, onDidChangeTextDocument, onDidSaveTextDocument', async () => { const file = await createRandomFile(); @@ -296,23 +293,20 @@ suite('vscode API - workspace', () => { await revertAllDirty(); // needed for a clean state for `onDidSaveTextDocument` (#102365) - const pendingAsserts: Function[] = []; - let onDidOpenTextDocument = false; + const onDidOpenTextDocument = new Set(); + const onDidChangeTextDocument = new Set(); + const onDidSaveTextDocument = new Set(); + disposables.push(vscode.workspace.onDidOpenTextDocument(e => { - pendingAsserts.push(() => assertEqualPath(e.uri.fsPath, file.fsPath)); - onDidOpenTextDocument = true; + onDidOpenTextDocument.add(e); })); - let onDidChangeTextDocument = false; disposables.push(vscode.workspace.onDidChangeTextDocument(e => { - pendingAsserts.push(() => assertEqualPath(e.document.uri.fsPath, file.fsPath)); - onDidChangeTextDocument = true; + onDidChangeTextDocument.add(e.document); })); - let onDidSaveTextDocument = false; disposables.push(vscode.workspace.onDidSaveTextDocument(e => { - pendingAsserts.push(() => assertEqualPath(e.uri.fsPath, file.fsPath)); - onDidSaveTextDocument = true; + onDidSaveTextDocument.add(e); })); const doc = await vscode.workspace.openTextDocument(file); @@ -323,10 +317,10 @@ suite('vscode API - workspace', () => { }); await doc.save(); - assert.ok(onDidOpenTextDocument); - assert.ok(onDidChangeTextDocument); - assert.ok(onDidSaveTextDocument); - pendingAsserts.forEach(assert => assert()); + assert.ok(Array.from(onDidOpenTextDocument).find(e => e.uri.toString() === file.toString()), 'did Open: ' + file.toString()); + assert.ok(Array.from(onDidChangeTextDocument).find(e => e.uri.toString() === file.toString()), 'did Change: ' + file.toString()); + assert.ok(Array.from(onDidSaveTextDocument).find(e => e.uri.toString() === file.toString()), 'did Save: ' + file.toString()); + disposeAll(disposables); return deleteFile(file); }); @@ -334,14 +328,13 @@ suite('vscode API - workspace', () => { test('events: onDidSaveTextDocument fires even for non dirty file when saved', async () => { const file = await createRandomFile(); const disposables: vscode.Disposable[] = []; - const pendingAsserts: Function[] = []; await revertAllDirty(); // needed for a clean state for `onDidSaveTextDocument` (#102365) - let onDidSaveTextDocument = false; + const onDidSaveTextDocument = new Set(); + disposables.push(vscode.workspace.onDidSaveTextDocument(e => { - pendingAsserts.push(() => assertEqualPath(e.uri.fsPath, file.fsPath)); - onDidSaveTextDocument = true; + onDidSaveTextDocument.add(e); })); const doc = await vscode.workspace.openTextDocument(file); @@ -349,7 +342,7 @@ suite('vscode API - workspace', () => { await vscode.commands.executeCommand('workbench.action.files.save'); assert.ok(onDidSaveTextDocument); - pendingAsserts.forEach(fn => fn()); + assert.ok(Array.from(onDidSaveTextDocument).find(e => e.uri.toString() === file.toString()), 'did Save: ' + file.toString()); disposeAll(disposables); return deleteFile(file); }); From 2d13e2222e370d7deb3692445452027602bb24e6 Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 1 Jul 2022 16:12:18 +0200 Subject: [PATCH 119/150] Improve layout modes and workbench layout implement MergeEditor#onDidChangeSizeConstraints and minimumWidth so that layout changes reflect better in the workbench, fixes https://github.com/microsoft/vscode/issues/151866 --- .../mergeEditor/browser/view/mergeEditor.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts index ae2fcbcd7f1..74629320cd2 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts @@ -10,6 +10,7 @@ import { IAction } from 'vs/base/common/actions'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Color } from 'vs/base/common/color'; import { BugIndicatingError } from 'vs/base/common/errors'; +import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; @@ -85,8 +86,6 @@ export class MergeEditor extends AbstractTextEditor { private readonly _sessionDisposables = new DisposableStore(); private _grid!: Grid; - - private readonly input1View = this._register(this.instantiation.createInstance(InputCodeEditorView, 1)); private readonly input2View = this._register(this.instantiation.createInstance(InputCodeEditorView, 2)); private readonly inputResultView = this._register(this.instantiation.createInstance(ResultCodeEditorView)); @@ -209,6 +208,19 @@ export class MergeEditor extends AbstractTextEditor { super.dispose(); } + // --- layout constraints + + private readonly _onDidChangeSizeConstraints = new Emitter(); + override readonly onDidChangeSizeConstraints: Event = this._onDidChangeSizeConstraints.event; + + override get minimumWidth() { + return this._layoutMode.value === 'mixed' + ? this.input1View.view.minimumWidth + this.input1View.view.minimumWidth + : this.input1View.view.minimumWidth + this.input1View.view.minimumWidth + this.inputResultView.view.minimumWidth; + } + + // --- + override getTitle(): string { if (this.input) { return this.input.getName(); @@ -454,6 +466,7 @@ export class MergeEditor extends AbstractTextEditor { } this._layoutMode.value = newValue; this._ctxUsesColumnLayout.set(newValue); + this._onDidChangeSizeConstraints.fire(); } private _applyViewState(state: IMergeEditorViewState | undefined) { From b9d702a2e2287fdf880e3edcf35d67486e40913a Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 1 Jul 2022 16:37:41 +0200 Subject: [PATCH 120/150] add an option to discard merge changes When opening a merge editor we take a snapshot of the result model. We use that snapshort to discard all merge changes so that you also restore the init state - independent of intermediate saves, https://github.com/microsoft/vscode/issues/152841#issuecomment-1167473537 --- .../mergeEditor/browser/mergeEditorInput.ts | 33 ++++++++++++++++--- .../browser/model/mergeEditorModel.ts | 5 ++- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts index 87aa4ac9b0c..137f5a9b992 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts @@ -22,6 +22,7 @@ import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/model import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { ILanguageSupport, ITextFileEditorModel, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { assertType } from 'vs/base/common/types'; export class MergeEditorInputData { constructor( @@ -190,13 +191,15 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements // manual-save: FYI and discard actions.push( localize('unhandledConflicts.manualSaveIgnore', "Save and Continue with Conflicts"), // 0 - localize('unhandledConflicts.manualSaveNoSave', "Don't Save") // 1 + localize('unhandledConflicts.discard', "Discard Merge Changes"), // 1 + localize('unhandledConflicts.manualSaveNoSave', "Don't Save"), // 2 ); } else { // auto-save: only FYI actions.push( localize('unhandledConflicts.ignore', "Continue with Conflicts"), // 0 + localize('unhandledConflicts.discard', "Discard Merge Changes"), // 1 ); } @@ -224,10 +227,32 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements if (choice === 0) { // conflicts: continue with remaining conflicts return ConfirmResult.SAVE; - } - // don't save - return ConfirmResult.DONT_SAVE; + } else if (choice === 1) { + // discard: undo all changes and save original (pre-merge) state + for (const input of inputs) { + input._discardMergeChanges(); + } + return ConfirmResult.SAVE; + + } else { + // don't save + return ConfirmResult.DONT_SAVE; + } + } + + private _discardMergeChanges(): void { + assertType(this._model !== undefined); + + const chunks: string[] = []; + while (true) { + const chunk = this._model.resultSnapshot.read(); + if (chunk === null) { + break; + } + chunks.push(chunk); + } + this._model.result.setValue(chunks.join()); } setLanguageId(languageId: string, _setExplicitly?: boolean): void { diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts index d9f61d89baf..b15973884a5 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts @@ -6,7 +6,7 @@ import { CompareResult, equals } from 'vs/base/common/arrays'; import { BugIndicatingError } from 'vs/base/common/errors'; import { ILanguageService } from 'vs/editor/common/languages/language'; -import { ITextModel } from 'vs/editor/common/model'; +import { ITextModel, ITextSnapshot } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; import { EditorModel } from 'vs/workbench/common/editor/editorModel'; import { autorunHandleChanges, derivedObservable, IObservable, IReader, ITransaction, keepAlive, ObservableValue, transaction, waitForState } from 'vs/workbench/contrib/audioCues/browser/observable'; @@ -120,6 +120,8 @@ export class MergeEditorModel extends EditorModel { ); }); + readonly resultSnapshot: ITextSnapshot; + constructor( readonly base: ITextModel, readonly input1: ITextModel, @@ -137,6 +139,7 @@ export class MergeEditorModel extends EditorModel { ) { super(); + this.resultSnapshot = result.createSnapshot(); this._register(keepAlive(this.modifiedBaseRangeStateStores)); this._register(keepAlive(this.modifiedBaseRangeHandlingStateStores)); this._register(keepAlive(this.input1ResultMapping)); From dc5fd78fb9701f3b9895a3224fe21721e676e8c2 Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 1 Jul 2022 17:45:19 +0200 Subject: [PATCH 121/150] use simple SPAN elements over `IconLabel` fixes https://github.com/microsoft/vscode/issues/151036 --- .../browser/view/editors/codeEditorView.ts | 19 +++++++++------- .../view/editors/resultCodeEditorView.ts | 9 ++++---- .../browser/view/media/mergeEditor.css | 22 ++++++++++++++++--- 3 files changed, 35 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts index e9a1b888ce0..49d4726b463 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { h } from 'vs/base/browser/dom'; +import { h, reset } from 'vs/base/browser/dom'; import { IView, IViewSize } from 'vs/base/browser/ui/grid/grid'; -import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; +import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { IEditorContributionDescription } from 'vs/editor/browser/editorExtensions'; @@ -24,7 +24,11 @@ export abstract class CodeEditorView extends Disposable { readonly model = this._viewModel.map(m => m?.model); protected readonly htmlElements = h('div.code-view', [ - h('div.title', { $: 'title' }), + h('div.title', [ + h('span.title', { $: 'title' }), + h('span.description', { $: 'description' }), + h('span.detail', { $: 'detail' }), + ]), h('div.container', [ h('div.gutter', { $: 'gutterDiv' }), h('div', { $: 'editor' }), @@ -53,9 +57,6 @@ export abstract class CodeEditorView extends Disposable { // snap?: boolean | undefined; }; - private readonly _title = new IconLabel(this.htmlElements.title, { supportIcons: true }); - protected readonly _detail = new IconLabel(this.htmlElements.title, { supportIcons: true }); - public readonly editor = this.instantiationService.createInstance( CodeEditorWidget, this.htmlElements.editor, @@ -100,8 +101,10 @@ export abstract class CodeEditorView extends Disposable { detail: string | undefined ): void { this.editor.setModel(textModel); - this._title.setLabel(title, description); - this._detail.setLabel('', detail); + + reset(this.htmlElements.title, ...renderLabelWithIcons(title)); + reset(this.htmlElements.description, ...(description ? renderLabelWithIcons(description) : [])); + reset(this.htmlElements.detail, ...(detail ? renderLabelWithIcons(detail) : [])); this._viewModel.set(viewModel, undefined); } diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts index fc6919af08a..84921d8ccd9 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts @@ -114,17 +114,18 @@ export class ResultCodeEditorView extends CodeEditorView { } const count = model.unhandledConflictsCount.read(reader); - this._detail.setLabel(count === 1 + this.htmlElements.detail.innerText = count === 1 ? localize( 'mergeEditor.remainingConflicts', - '{0} Remaining Conflict', + '{0} Conflict Remaining', count ) : localize( 'mergeEditor.remainingConflict', - '{0} Remaining Conflicts', + '{0} Conflicts Remaining ', count - )); + ); + }, 'update label')); } } diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/media/mergeEditor.css b/src/vs/workbench/contrib/mergeEditor/browser/view/media/mergeEditor.css index b832e88bd84..e9c0ada7df3 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/media/mergeEditor.css +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/media/mergeEditor.css @@ -8,11 +8,27 @@ height: 30px; display: flex; align-content: center; - justify-content: space-between; } -.monaco-workbench .merge-editor .code-view > .title .monaco-icon-label { - margin: auto 0; +.monaco-workbench .merge-editor .code-view > .title>SPAN { + align-self: center; + text-overflow: ellipsis; + overflow: hidden; + padding-right: 6px; +} + +.monaco-workbench .merge-editor .code-view > .title>SPAN.title { + flex-shrink: 0; +} + +.monaco-workbench .merge-editor .code-view > .title>SPAN.description { + opacity: 0.7; + font-size: 90%; +} + +.monaco-workbench .merge-editor .code-view > .title>SPAN.detail { + margin-left: auto; + flex-shrink: 0; } .monaco-workbench .merge-editor .code-view > .container { From 623cde6b38b8238dbe33a7e4536f5917a69df1b3 Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Fri, 1 Jul 2022 09:20:19 -0700 Subject: [PATCH 122/150] Disable inline completions by default for Emmet (#153926) Ref #139247 --- extensions/emmet/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/emmet/package.json b/extensions/emmet/package.json index 91c06bc3a26..d9deee50fb3 100644 --- a/extensions/emmet/package.json +++ b/extensions/emmet/package.json @@ -129,7 +129,7 @@ }, "emmet.useInlineCompletions": { "type": "boolean", - "default": true, + "default": false, "markdownDescription": "%emmetUseInlineCompletions%" }, "emmet.preferences": { From f4ab3480cbd3d549b020ba04e37eaa66190db176 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Fri, 1 Jul 2022 10:11:20 -0700 Subject: [PATCH 123/150] have edit sessions use ILocalizedString (#153933) --- .../browser/sessionSync.contribution.ts | 24 +++++++++---------- .../browser/sessionSyncWorkbenchService.ts | 5 ++-- .../sessionSync/common/sessionSync.ts | 6 ++++- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts b/src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts index d075977cb45..08381fc3d9c 100644 --- a/src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts +++ b/src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts @@ -10,7 +10,7 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { localize } from 'vs/nls'; -import { ISessionSyncWorkbenchService, Change, ChangeType, Folder, EditSession, FileType, EDIT_SESSION_SYNC_TITLE, EditSessionSchemaVersion } from 'vs/workbench/services/sessionSync/common/sessionSync'; +import { ISessionSyncWorkbenchService, Change, ChangeType, Folder, EditSession, FileType, EDIT_SESSION_SYNC_CATEGORY, EditSessionSchemaVersion } from 'vs/workbench/services/sessionSync/common/sessionSync'; import { ISCMRepository, ISCMService } from 'vs/workbench/contrib/scm/common/scm'; import { IFileService } from 'vs/platform/files/common/files'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -44,11 +44,13 @@ registerSingleton(ISessionSyncWorkbenchService, SessionSyncWorkbenchService); const resumeLatestCommand = { id: 'workbench.experimental.editSessions.actions.resumeLatest', - title: { value: localize('resume latest', "{0}: Resume Latest Edit Session", EDIT_SESSION_SYNC_TITLE), original: 'Edit Sessions' }, + title: { value: localize('resume latest', "Resume Latest Edit Session"), original: 'Resume Latest Edit Session' }, + category: EDIT_SESSION_SYNC_CATEGORY, }; const storeCurrentCommand = { id: 'workbench.experimental.editSessions.actions.storeCurrent', - title: { value: localize('store current', "{0}: Store Current Edit Session", EDIT_SESSION_SYNC_TITLE), original: 'Edit Sessions' }, + title: { value: localize('store current', "Store Current Edit Session"), original: 'Store Current Edit Session' }, + category: EDIT_SESSION_SYNC_CATEGORY, }; const continueEditSessionCommand = { id: '_workbench.experimental.editSessions.actions.continueEditSession', @@ -56,7 +58,7 @@ const continueEditSessionCommand = { }; const openLocalFolderCommand = { id: '_workbench.experimental.editSessions.actions.continueEditSession.openLocalFolder', - title: localize('continue edit session in local folder', "Open In Local Folder"), + title: { value: localize('continue edit session in local folder', "Open In Local Folder"), original: 'Open In Local Folder' }, }; const queryParamName = 'editSessionId'; @@ -146,8 +148,7 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon this._register(registerAction2(class ContinueEditSessionAction extends Action2 { constructor() { super({ - id: continueEditSessionCommand.id, - title: continueEditSessionCommand.title, + ...continueEditSessionCommand, f1: true }); } @@ -181,8 +182,7 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon this._register(registerAction2(class ApplyLatestEditSessionAction extends Action2 { constructor() { super({ - id: resumeLatestCommand.id, - title: resumeLatestCommand.title, + ...resumeLatestCommand, menu: { id: MenuId.CommandPalette, } @@ -203,8 +203,7 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon this._register(registerAction2(class StoreLatestEditSessionAction extends Action2 { constructor() { super({ - id: storeCurrentCommand.id, - title: storeCurrentCommand.title, + ...storeCurrentCommand, menu: { id: MenuId.CommandPalette, } @@ -275,7 +274,7 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon const result = await this.dialogService.confirm({ message: localize('apply edit session warning', 'Applying your edit session may overwrite your existing uncommitted changes. Do you want to proceed?'), type: 'warning', - title: EDIT_SESSION_SYNC_TITLE + title: EDIT_SESSION_SYNC_CATEGORY.value }); if (!result.confirmed) { return; @@ -399,8 +398,7 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon this._register(registerAction2(class ContinueInLocalFolderAction extends Action2 { constructor() { super({ - id: openLocalFolderCommand.id, - title: openLocalFolderCommand.title, + ...openLocalFolderCommand, precondition: IsWebContext }); } diff --git a/src/vs/workbench/services/sessionSync/browser/sessionSyncWorkbenchService.ts b/src/vs/workbench/services/sessionSync/browser/sessionSyncWorkbenchService.ts index 2c0a8bb167e..4274c62031d 100644 --- a/src/vs/workbench/services/sessionSync/browser/sessionSyncWorkbenchService.ts +++ b/src/vs/workbench/services/sessionSync/browser/sessionSyncWorkbenchService.ts @@ -19,7 +19,7 @@ import { IAuthenticationProvider } from 'vs/platform/userDataSync/common/userDat import { UserDataSyncStoreClient } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; import { AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticationService } from 'vs/workbench/services/authentication/common/authentication'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { EDIT_SESSIONS_SIGNED_IN, EditSession, EDIT_SESSION_SYNC_TITLE, ISessionSyncWorkbenchService, EDIT_SESSIONS_SIGNED_IN_KEY } from 'vs/workbench/services/sessionSync/common/sessionSync'; +import { EDIT_SESSIONS_SIGNED_IN, EditSession, EDIT_SESSION_SYNC_CATEGORY, ISessionSyncWorkbenchService, EDIT_SESSIONS_SIGNED_IN_KEY } from 'vs/workbench/services/sessionSync/common/sessionSync'; type ExistingSession = IQuickPickItem & { session: AuthenticationSession & { providerId: string } }; type AuthenticationProviderOption = IQuickPickItem & { provider: IAuthenticationProvider }; @@ -337,7 +337,8 @@ export class SessionSyncWorkbenchService extends Disposable implements ISessionS constructor() { super({ id: 'workbench.sessionSync.actions.resetAuth', - title: localize('reset auth', '{0}: Sign Out', EDIT_SESSION_SYNC_TITLE), + title: localize('reset auth', 'Sign Out'), + category: EDIT_SESSION_SYNC_CATEGORY, precondition: ContextKeyExpr.equals(EDIT_SESSIONS_SIGNED_IN_KEY, true), menu: [{ id: MenuId.CommandPalette, diff --git a/src/vs/workbench/services/sessionSync/common/sessionSync.ts b/src/vs/workbench/services/sessionSync/common/sessionSync.ts index 6eafdb65490..c671ae2aeab 100644 --- a/src/vs/workbench/services/sessionSync/common/sessionSync.ts +++ b/src/vs/workbench/services/sessionSync/common/sessionSync.ts @@ -4,10 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; +import { ILocalizedString } from 'vs/platform/action/common/action'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -export const EDIT_SESSION_SYNC_TITLE = localize('session sync', 'Edit Sessions'); +export const EDIT_SESSION_SYNC_CATEGORY: ILocalizedString = { + original: 'Edit Sessions', + value: localize('session sync', 'Edit Sessions') +}; export const ISessionSyncWorkbenchService = createDecorator('ISessionSyncWorkbenchService'); export interface ISessionSyncWorkbenchService { From 46234601a20b70c50873a53b3a8b8477946f846e Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Fri, 1 Jul 2022 11:04:11 -0700 Subject: [PATCH 124/150] Allow to specify language id in command to create a new untitled file (#153872) * Allow to specify language id in command to create a new untitled file * Make viewtype optional --- .../workbench/contrib/files/browser/fileCommands.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index 2641877c8cd..67ab692b603 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -628,21 +628,23 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ args: [ { isOptional: true, - name: 'viewType', - description: 'The editor view type', + name: 'New Untitled File args', + description: 'The editor view type and language ID if known', schema: { 'type': 'object', - 'required': ['viewType'], 'properties': { 'viewType': { 'type': 'string' + }, + 'languageId': { + 'type': 'string' } } } } ] }, - handler: async (accessor, args?: { viewType: string }) => { + handler: async (accessor, args?: { languageId?: string; viewType?: string }) => { const editorService = accessor.get(IEditorService); await editorService.openEditor({ @@ -650,7 +652,8 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ options: { override: args?.viewType, pinned: true - } + }, + languageId: args?.languageId, }); } }); From fecd1aa404ee0faaaf279e0e76bfd32277edf5af Mon Sep 17 00:00:00 2001 From: SteVen Batten <6561887+sbatten@users.noreply.github.com> Date: Fri, 1 Jul 2022 12:46:26 -0700 Subject: [PATCH 125/150] update command title for better localization (#153940) refs #153865 --- src/vs/workbench/browser/actions/layoutActions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index 4b4cc8b38f0..9bf977172a8 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -1182,7 +1182,7 @@ registerAction2(class CustomizeLayoutAction extends Action2 { constructor() { super({ id: 'workbench.action.customizeLayout', - title: localize('customizeLayout', "Customize Layout..."), + title: { original: 'Customize Layout...', value: localize('customizeLayout', "Customize Layout...") }, f1: true, icon: configureLayoutIcon, menu: [ From 71b996eff4cef4405d1d1cc4fe308545dac0b255 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Fri, 1 Jul 2022 14:41:48 -0700 Subject: [PATCH 126/150] use ILocalizedString in a few places (#153950) --- .../browser/languageDetection.contribution.ts | 2 +- .../notebook/browser/controller/editActions.ts | 2 +- .../contrib/update/browser/update.contribution.ts | 12 ++++++------ .../browser/gettingStartedService.ts | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/languageDetection/browser/languageDetection.contribution.ts b/src/vs/workbench/contrib/languageDetection/browser/languageDetection.contribution.ts index a931e2bb2f2..ef593cb3de1 100644 --- a/src/vs/workbench/contrib/languageDetection/browser/languageDetection.contribution.ts +++ b/src/vs/workbench/contrib/languageDetection/browser/languageDetection.contribution.ts @@ -123,7 +123,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: detectLanguageCommandId, - title: localize('detectlang', 'Detect Language from Content'), + title: { value: localize('detectlang', 'Detect Language from Content'), original: 'Detect Language from Content' }, f1: true, precondition: ContextKeyExpr.and(NOTEBOOK_EDITOR_EDITABLE.toNegated(), EditorContextKeys.editorTextFocus), keybinding: { primary: KeyCode.KeyD | KeyMod.Alt | KeyMod.Shift, weight: KeybindingWeight.WorkbenchContrib } diff --git a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts index 7250d2dc560..ebcdf94c782 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts @@ -469,7 +469,7 @@ registerAction2(class DetectCellLanguageAction extends NotebookCellAction { constructor() { super({ id: DETECT_CELL_LANGUAGE, - title: localize('detectLanguage', 'Accept Detected Language for Cell'), + title: { value: localize('detectLanguage', 'Accept Detected Language for Cell'), original: 'Accept Detected Language for Cell' }, f1: true, precondition: ContextKeyExpr.and(NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE), keybinding: { primary: KeyCode.KeyD | KeyMod.Alt | KeyMod.Shift, weight: KeybindingWeight.WorkbenchContrib } diff --git a/src/vs/workbench/contrib/update/browser/update.contribution.ts b/src/vs/workbench/contrib/update/browser/update.contribution.ts index 5d22660c689..1ff72278d33 100644 --- a/src/vs/workbench/contrib/update/browser/update.contribution.ts +++ b/src/vs/workbench/contrib/update/browser/update.contribution.ts @@ -34,8 +34,8 @@ class DownloadUpdateAction extends Action2 { constructor() { super({ id: 'update.downloadUpdate', - title: localize('downloadUpdate', "Download Update"), - category: product.nameShort, + title: { value: localize('downloadUpdate', "Download Update"), original: 'Download Update' }, + category: { value: product.nameShort, original: product.nameShort }, f1: true, precondition: CONTEXT_UPDATE_STATE.isEqualTo(StateType.AvailableForDownload) }); @@ -50,8 +50,8 @@ class InstallUpdateAction extends Action2 { constructor() { super({ id: 'update.installUpdate', - title: localize('installUpdate', "Install Update"), - category: product.nameShort, + title: { value: localize('installUpdate', "Install Update"), original: 'Install Update' }, + category: { value: product.nameShort, original: product.nameShort }, f1: true, precondition: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Downloaded) }); @@ -66,8 +66,8 @@ class RestartToUpdateAction extends Action2 { constructor() { super({ id: 'update.restartToUpdate', - title: localize('restartToUpdate', "Restart to Update"), - category: product.nameShort, + title: { value: localize('restartToUpdate', "Restart to Update"), original: 'Restart to Update' }, + category: { value: product.nameShort, original: product.nameShort }, f1: true, precondition: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Ready) }); diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts index 74b541681a6..dcbddfc4097 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts @@ -691,8 +691,8 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'resetGettingStartedProgress', - category: 'Developer', - title: 'Reset Welcome Page Walkthrough Progress', + category: { original: 'Developer', value: localize('developer', "Developer") }, + title: { original: 'Reset Welcome Page Walkthrough Progress', value: localize('resetWelcomePageWalkthroughProgress', "Reset Welcome Page Walkthrough Progress") }, f1: true }); } From 7c78640d86e5de1ca0270912584d9d38beafeec1 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sat, 2 Jul 2022 18:24:41 +0200 Subject: [PATCH 127/150] Cannot read properties of undefined (reading 'resource') (fix #153361) (#153960) --- src/vs/workbench/services/history/browser/historyService.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/workbench/services/history/browser/historyService.ts b/src/vs/workbench/services/history/browser/historyService.ts index e0fb71b15ed..6f73bd54add 100644 --- a/src/vs/workbench/services/history/browser/historyService.ts +++ b/src/vs/workbench/services/history/browser/historyService.ts @@ -993,6 +993,10 @@ export class HistoryService extends Disposable implements IHistoryService { try { const entriesParsed: ISerializedEditorHistoryEntry[] = JSON.parse(entriesRaw); for (const entryParsed of entriesParsed) { + if (!entryParsed.editor || !entryParsed.editor.resource) { + continue; // unexpected data format + } + try { entries.push({ ...entryParsed.editor, From c01da07ec15759e8bb532468b2cb0f1fcf0eba93 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Sun, 3 Jul 2022 19:07:03 +0200 Subject: [PATCH 128/150] Fixes #153898. --- .../editor/browser/widget/codeEditorWidget.ts | 26 +++++++++++++++---- .../editor/common/viewLayout/linesLayout.ts | 18 +++++++++++-- src/vs/editor/common/viewLayout/viewLayout.ts | 7 +++-- .../mergeEditor/browser/view/editorGutter.ts | 22 ++++------------ 4 files changed, 47 insertions(+), 26 deletions(-) diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 1ba384d520f..f4e8c0d10db 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -564,27 +564,43 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE return this._modelData.viewModel.viewLayout.getWhitespaces(); } - private static _getVerticalOffsetForPosition(modelData: ModelData, modelLineNumber: number, modelColumn: number): number { + private static _getVerticalOffsetAfterPosition(modelData: ModelData, modelLineNumber: number, modelColumn: number, includeViewZones: boolean): number { const modelPosition = modelData.model.validatePosition({ lineNumber: modelLineNumber, column: modelColumn }); const viewPosition = modelData.viewModel.coordinatesConverter.convertModelPositionToViewPosition(modelPosition); - return modelData.viewModel.viewLayout.getVerticalOffsetForLineNumber(viewPosition.lineNumber); + return modelData.viewModel.viewLayout.getVerticalOffsetAfterLineNumber(viewPosition.lineNumber, includeViewZones); } - public getTopForLineNumber(lineNumber: number): number { + public getTopForLineNumber(lineNumber: number, includeViewZones: boolean = false): number { if (!this._modelData) { return -1; } - return CodeEditorWidget._getVerticalOffsetForPosition(this._modelData, lineNumber, 1); + return CodeEditorWidget._getVerticalOffsetForPosition(this._modelData, lineNumber, 1, includeViewZones); } public getTopForPosition(lineNumber: number, column: number): number { if (!this._modelData) { return -1; } - return CodeEditorWidget._getVerticalOffsetForPosition(this._modelData, lineNumber, column); + return CodeEditorWidget._getVerticalOffsetForPosition(this._modelData, lineNumber, column, false); + } + + private static _getVerticalOffsetForPosition(modelData: ModelData, modelLineNumber: number, modelColumn: number, includeViewZones: boolean = false): number { + const modelPosition = modelData.model.validatePosition({ + lineNumber: modelLineNumber, + column: modelColumn + }); + const viewPosition = modelData.viewModel.coordinatesConverter.convertModelPositionToViewPosition(modelPosition); + return modelData.viewModel.viewLayout.getVerticalOffsetForLineNumber(viewPosition.lineNumber, includeViewZones); + } + + public getBottomForLineNumber(lineNumber: number, includeViewZones: boolean = false): number { + if (!this._modelData) { + return -1; + } + return CodeEditorWidget._getVerticalOffsetAfterPosition(this._modelData, lineNumber, 1, includeViewZones); } public setHiddenAreas(ranges: IRange[]): void { diff --git a/src/vs/editor/common/viewLayout/linesLayout.ts b/src/vs/editor/common/viewLayout/linesLayout.ts index 01e2d26cf5a..7bb55aeef6e 100644 --- a/src/vs/editor/common/viewLayout/linesLayout.ts +++ b/src/vs/editor/common/viewLayout/linesLayout.ts @@ -482,7 +482,7 @@ export class LinesLayout { * @param lineNumber The line number * @return The sum of heights for all objects above `lineNumber`. */ - public getVerticalOffsetForLineNumber(lineNumber: number): number { + public getVerticalOffsetForLineNumber(lineNumber: number, includeViewZones = false): number { this._checkPendingChanges(); lineNumber = lineNumber | 0; @@ -493,11 +493,25 @@ export class LinesLayout { previousLinesHeight = 0; } - const previousWhitespacesHeight = this.getWhitespaceAccumulatedHeightBeforeLineNumber(lineNumber); + const previousWhitespacesHeight = this.getWhitespaceAccumulatedHeightBeforeLineNumber(lineNumber - (includeViewZones ? 1 : 0)); return previousLinesHeight + previousWhitespacesHeight + this._paddingTop; } + /** + * Get the vertical offset (the sum of heights for all objects above) a certain line number. + * + * @param lineNumber The line number + * @return The sum of heights for all objects above `lineNumber`. + */ + public getVerticalOffsetAfterLineNumber(lineNumber: number, includeViewZones = false): number { + this._checkPendingChanges(); + lineNumber = lineNumber | 0; + const previousLinesHeight = this._lineHeight * lineNumber; + const previousWhitespacesHeight = this.getWhitespaceAccumulatedHeightBeforeLineNumber(lineNumber + (includeViewZones ? 1 : 0)); + return previousLinesHeight + previousWhitespacesHeight + this._paddingTop; + } + /** * Returns if there is any whitespace in the document. */ diff --git a/src/vs/editor/common/viewLayout/viewLayout.ts b/src/vs/editor/common/viewLayout/viewLayout.ts index fa5013cac62..0acbe29204f 100644 --- a/src/vs/editor/common/viewLayout/viewLayout.ts +++ b/src/vs/editor/common/viewLayout/viewLayout.ts @@ -362,8 +362,11 @@ export class ViewLayout extends Disposable implements IViewLayout { } return hadAChange; } - public getVerticalOffsetForLineNumber(lineNumber: number): number { - return this._linesLayout.getVerticalOffsetForLineNumber(lineNumber); + public getVerticalOffsetForLineNumber(lineNumber: number, includeViewZones: boolean = false): number { + return this._linesLayout.getVerticalOffsetForLineNumber(lineNumber, includeViewZones); + } + public getVerticalOffsetAfterLineNumber(lineNumber: number, includeViewZones: boolean = false): number { + return this._linesLayout.getVerticalOffsetAfterLineNumber(lineNumber, includeViewZones); } public isAfterLines(verticalOffset: number): boolean { return this._linesLayout.isAfterLines(verticalOffset); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts index 2b208650738..08a13900476 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts @@ -6,7 +6,6 @@ import { h } from 'vs/base/browser/dom'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; -import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable'; import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange'; @@ -68,15 +67,13 @@ export class EditorGutter extends D const visibleRange2 = new LineRange( visibleRange.startLineNumber, visibleRange.endLineNumber - visibleRange.startLineNumber - ); + ).deltaEnd(1); const gutterItems = this.itemProvider.getIntersectingGutterItems( visibleRange2, reader ); - const lineHeight = this._editor.getOptions().get(EditorOption.lineHeight); - for (const gutterItem of gutterItems) { if (!gutterItem.range.touches(visibleRange2)) { continue; @@ -99,19 +96,10 @@ export class EditorGutter extends D } const top = - (gutterItem.range.startLineNumber === 1 - ? -lineHeight - : this._editor.getTopForLineNumber( - gutterItem.range.startLineNumber - 1 - )) - - scrollTop + - lineHeight; - - const bottom = ( - gutterItem.range.endLineNumberExclusive <= this._editor.getModel()!.getLineCount() - ? this._editor.getTopForLineNumber(gutterItem.range.endLineNumberExclusive) - : this._editor.getTopForLineNumber(gutterItem.range.endLineNumberExclusive - 1) + lineHeight - ) - scrollTop; + gutterItem.range.startLineNumber <= this._editor.getModel()!.getLineCount() + ? this._editor.getTopForLineNumber(gutterItem.range.startLineNumber, true) - scrollTop + : this._editor.getBottomForLineNumber(gutterItem.range.startLineNumber - 1, false) - scrollTop; + const bottom = this._editor.getBottomForLineNumber(gutterItem.range.endLineNumberExclusive - 1, true) - scrollTop; const height = bottom - top; From 20324ac1370af77899d239d26000b3cd32ff4981 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Sun, 3 Jul 2022 19:06:40 +0200 Subject: [PATCH 129/150] navigation now only considers actual conflicts. --- .../mergeEditor/browser/commands/commands.ts | 4 +-- .../mergeEditor/browser/view/viewModel.ts | 29 +++++++++++++------ 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts b/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts index 4fa542c981d..375c202ea6a 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts @@ -175,7 +175,7 @@ export class GoToNextConflict extends Action2 { run(accessor: ServicesAccessor): void { const { activeEditorPane } = accessor.get(IEditorService); if (activeEditorPane instanceof MergeEditor) { - activeEditorPane.viewModel.get()?.goToNextConflict(); + activeEditorPane.viewModel.get()?.goToNextModifiedBaseRange(true); } } } @@ -200,7 +200,7 @@ export class GoToPreviousConflict extends Action2 { run(accessor: ServicesAccessor): void { const { activeEditorPane } = accessor.get(IEditorService); if (activeEditorPane instanceof MergeEditor) { - activeEditorPane.viewModel.get()?.goToPreviousConflict(); + activeEditorPane.viewModel.get()?.goToPreviousModifiedBaseRange(true); } } } diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts index b53549fea4b..3f90643ea4a 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts @@ -3,13 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { findLast, lastOrDefault } from 'vs/base/common/arrays'; +import { findLast } from 'vs/base/common/arrays'; import { ScrollType } from 'vs/editor/common/editorCommon'; import { derivedObservable, derivedObservableWithWritableCache, IReader, ITransaction, ObservableValue, transaction } from 'vs/workbench/contrib/audioCues/browser/observable'; import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange'; import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel'; import { ModifiedBaseRange, ModifiedBaseRangeState } from 'vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange'; -import { elementAtOrUndefined } from 'vs/workbench/contrib/mergeEditor/browser/utils'; import { CodeEditorView } from 'vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView'; import { InputCodeEditorView } from 'vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView'; import { ResultCodeEditorView } from 'vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView'; @@ -78,7 +77,7 @@ export class MergeEditorViewModel { this.model.setState(baseRange, state, true, tx); } - public goToConflict(getModifiedBaseRange: (editor: CodeEditorView, curLineNumber: number) => ModifiedBaseRange | undefined): void { + private goToConflict(getModifiedBaseRange: (editor: CodeEditorView, curLineNumber: number) => ModifiedBaseRange | undefined): void { const lastFocusedEditor = this.lastFocusedEditor.get(); if (!lastFocusedEditor) { return; @@ -98,23 +97,35 @@ export class MergeEditorViewModel { } } - public goToNextConflict(): void { + public goToNextModifiedBaseRange(onlyConflicting: boolean): void { this.goToConflict( (e, l) => this.model.modifiedBaseRanges .get() - .find((r) => this.getRange(e, r, undefined).startLineNumber > l) || - elementAtOrUndefined(this.model.modifiedBaseRanges.get(), 0) + .find( + (r) => + (!onlyConflicting || r.isConflicting) && + this.getRange(e, r, undefined).startLineNumber > l + ) || + this.model.modifiedBaseRanges + .get() + .find((r) => !onlyConflicting || r.isConflicting) ); } - public goToPreviousConflict(): void { + public goToPreviousModifiedBaseRange(onlyConflicting: boolean): void { this.goToConflict( (e, l) => findLast( this.model.modifiedBaseRanges.get(), - (r) => this.getRange(e, r, undefined).endLineNumberExclusive < l - ) || lastOrDefault(this.model.modifiedBaseRanges.get()) + (r) => + (!onlyConflicting || r.isConflicting) && + this.getRange(e, r, undefined).endLineNumberExclusive < l + ) || + findLast( + this.model.modifiedBaseRanges.get(), + (r) => !onlyConflicting || r.isConflicting + ) ); } From fc90795932e82ab75b2a69d52f6103a3a86f05a3 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 1 Jul 2022 18:26:11 +0200 Subject: [PATCH 130/150] Use middle click on a checkbox to toggle its state. --- .../view/editors/inputCodeEditorView.ts | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts index 1610a9a0b0d..aadd274e6e1 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts @@ -140,6 +140,21 @@ export class InputCodeEditorView extends CodeEditorView { .withInputValue(this.inputNumber, value), tx ), + toggleBothSides() { + transaction(tx => { + const state = model + .getState(baseRange) + .get(); + model.setState( + baseRange, + state + .toggle(inputNumber) + .toggle(inputNumber === 1 ? 2 : 1), + true, + tx + ); + }); + }, getContextMenuActions: () => { const state = model.getState(baseRange).get(); const handled = model.isHandled(baseRange).get(); @@ -233,6 +248,7 @@ export interface ModifiedBaseRangeGutterItemInfo extends IGutterItemInfo { enabled: IObservable; toggleState: IObservable; setState(value: boolean, tx: ITransaction): void; + toggleBothSides(): void; getContextMenuActions(): readonly IAction[]; } @@ -257,22 +273,28 @@ export class MergeConflictGutterItemView extends Disposable implements IGutterIt this._register( dom.addDisposableListener(checkBox.domNode, dom.EventType.MOUSE_DOWN, (e) => { - if (e.button === 2) { - const item = this.item.get(); - if (!item) { - return; - } + const item = this.item.get(); + if (!item) { + return; + } + if (e.button === /* Right */ 2) { contextMenuService.showContextMenu({ getAnchor: () => checkBox.domNode, getActions: item.getContextMenuActions, }); + e.stopPropagation(); + e.preventDefault(); + } else if (e.button === /* Middle */ 1) { + item.toggleBothSides(); + e.stopPropagation(); e.preventDefault(); } }) ); + checkBox.domNode.classList.add('accept-conflict-group'); this._register( From 63d90d3a34697d2a008d45da3a6460ec9a602352 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Sun, 3 Jul 2022 19:45:01 +0200 Subject: [PATCH 131/150] Moves observables to base/common. Adds observable tests & logging. --- src/vs/base/common/observable.ts | 30 + src/vs/base/common/observableImpl/autorun.ts | 167 +++++ src/vs/base/common/observableImpl/base.ts | 244 ++++++ src/vs/base/common/observableImpl/derived.ts | 167 +++++ src/vs/base/common/observableImpl/logging.ts | 312 ++++++++ src/vs/base/common/observableImpl/utils.ts | 281 +++++++ src/vs/base/test/common/observable.test.ts | 468 ++++++++++++ .../audioCueLineFeatureContribution.ts | 9 +- .../audioCues/browser/audioCueService.ts | 10 +- .../contrib/audioCues/browser/observable.ts | 706 +----------------- .../audioCues/test/browser/observable.test.ts | 468 ++++++++++++ 11 files changed, 2175 insertions(+), 687 deletions(-) create mode 100644 src/vs/base/common/observable.ts create mode 100644 src/vs/base/common/observableImpl/autorun.ts create mode 100644 src/vs/base/common/observableImpl/base.ts create mode 100644 src/vs/base/common/observableImpl/derived.ts create mode 100644 src/vs/base/common/observableImpl/logging.ts create mode 100644 src/vs/base/common/observableImpl/utils.ts create mode 100644 src/vs/base/test/common/observable.test.ts create mode 100644 src/vs/workbench/contrib/audioCues/test/browser/observable.test.ts diff --git a/src/vs/base/common/observable.ts b/src/vs/base/common/observable.ts new file mode 100644 index 00000000000..c588d68b9f6 --- /dev/null +++ b/src/vs/base/common/observable.ts @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export { + IObservable, + IObserver, + IReader, + ISettable, + ISettableObservable, + ITransaction, + observableValue, + transaction, +} from 'vs/base/common/observableImpl/base'; +export { derived } from 'vs/base/common/observableImpl/derived'; +export { + autorun, + autorunDelta, + autorunHandleChanges, + autorunWithStore, +} from 'vs/base/common/observableImpl/autorun'; +export * from 'vs/base/common/observableImpl/utils'; + +import { ConsoleObservableLogger, setLogger } from 'vs/base/common/observableImpl/logging'; + +const enableLogging = false; +if (enableLogging) { + setLogger(new ConsoleObservableLogger()); +} diff --git a/src/vs/base/common/observableImpl/autorun.ts b/src/vs/base/common/observableImpl/autorun.ts new file mode 100644 index 00000000000..6efe4736783 --- /dev/null +++ b/src/vs/base/common/observableImpl/autorun.ts @@ -0,0 +1,167 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IReader, IObservable, IObserver } from 'vs/base/common/observableImpl/base'; +import { getLogger } from 'vs/base/common/observableImpl/logging'; + +export function autorun(debugName: string, fn: (reader: IReader) => void): IDisposable { + return new AutorunObserver(debugName, fn, undefined); +} + +interface IChangeContext { + readonly changedObservable: IObservable; + readonly change: unknown; + + didChange(observable: IObservable): this is { change: TChange }; +} + +export function autorunHandleChanges( + debugName: string, + options: { + /** + * Returns if this change should cause a re-run of the autorun. + */ + handleChange: (context: IChangeContext) => boolean; + }, + fn: (reader: IReader) => void +): IDisposable { + return new AutorunObserver(debugName, fn, options.handleChange); +} + +export function autorunWithStore( + fn: (reader: IReader, store: DisposableStore) => void, + debugName: string +): IDisposable { + const store = new DisposableStore(); + const disposable = autorun( + debugName, + reader => { + store.clear(); + fn(reader, store); + } + ); + return toDisposable(() => { + disposable.dispose(); + store.dispose(); + }); +} + +export class AutorunObserver implements IObserver, IReader, IDisposable { + public needsToRun = true; + private updateCount = 0; + private disposed = false; + + /** + * The actual dependencies. + */ + private _dependencies = new Set>(); + public get dependencies() { + return this._dependencies; + } + + /** + * Dependencies that have to be removed when {@link runFn} ran through. + */ + private staleDependencies = new Set>(); + + constructor( + public readonly debugName: string, + private readonly runFn: (reader: IReader) => void, + private readonly _handleChange: ((context: IChangeContext) => boolean) | undefined + ) { + getLogger()?.handleAutorunCreated(this); + this.runIfNeeded(); + } + + public subscribeTo(observable: IObservable) { + // In case the run action disposes the autorun + if (this.disposed) { + return; + } + this._dependencies.add(observable); + if (!this.staleDependencies.delete(observable)) { + observable.addObserver(this); + } + } + + public handleChange(observable: IObservable, change: TChange): void { + const shouldReact = this._handleChange ? this._handleChange({ + changedObservable: observable, + change, + didChange: o => o === observable as any, + }) : true; + this.needsToRun = this.needsToRun || shouldReact; + + if (this.updateCount === 0) { + this.runIfNeeded(); + } + } + + public beginUpdate(): void { + this.updateCount++; + } + + public endUpdate(): void { + this.updateCount--; + if (this.updateCount === 0) { + this.runIfNeeded(); + } + } + + private runIfNeeded(): void { + if (!this.needsToRun) { + return; + } + // Assert: this.staleDependencies is an empty set. + const emptySet = this.staleDependencies; + this.staleDependencies = this._dependencies; + this._dependencies = emptySet; + + this.needsToRun = false; + + getLogger()?.handleAutorunTriggered(this); + + try { + this.runFn(this); + } finally { + // We don't want our observed observables to think that they are (not even temporarily) not being observed. + // Thus, we only unsubscribe from observables that are definitely not read anymore. + for (const o of this.staleDependencies) { + o.removeObserver(this); + } + this.staleDependencies.clear(); + } + } + + public dispose(): void { + this.disposed = true; + for (const o of this._dependencies) { + o.removeObserver(this); + } + this._dependencies.clear(); + } + + public toString(): string { + return `Autorun<${this.debugName}>`; + } +} + +export namespace autorun { + export const Observer = AutorunObserver; +} +export function autorunDelta( + name: string, + observable: IObservable, + handler: (args: { lastValue: T | undefined; newValue: T }) => void +): IDisposable { + let _lastValue: T | undefined; + return autorun(name, (reader) => { + const newValue = observable.read(reader); + const lastValue = _lastValue; + _lastValue = newValue; + handler({ lastValue, newValue }); + }); +} diff --git a/src/vs/base/common/observableImpl/base.ts b/src/vs/base/common/observableImpl/base.ts new file mode 100644 index 00000000000..fd91e6f8d8d --- /dev/null +++ b/src/vs/base/common/observableImpl/base.ts @@ -0,0 +1,244 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import type { derived } from 'vs/base/common/observableImpl/derived'; +import { getLogger } from 'vs/base/common/observableImpl/logging'; + +export interface IObservable { + readonly TChange: TChange; + + /** + * Reads the current value. + * + * Must not be called from {@link IObserver.handleChange}. + */ + get(): T; + + /** + * Adds an observer. + */ + addObserver(observer: IObserver): void; + removeObserver(observer: IObserver): void; + + /** + * Subscribes the reader to this observable and returns the current value of this observable. + */ + read(reader: IReader): T; + + map(fn: (value: T) => TNew): IObservable; + + readonly debugName: string; +} + +export interface IReader { + /** + * Reports an observable that was read. + * + * Is called by {@link IObservable.read}. + */ + subscribeTo(observable: IObservable): void; +} + +export interface IObserver { + /** + * Indicates that an update operation is about to begin. + * + * During an update, invariants might not hold for subscribed observables and + * change events might be delayed. + * However, all changes must be reported before all update operations are over. + */ + beginUpdate(observable: IObservable): void; + + /** + * Is called by a subscribed observable immediately after it notices a change. + * + * When {@link IObservable.get} returns and no change has been reported, + * there has been no change for that observable. + * + * Implementations must not call into other observables! + * The change should be processed when {@link IObserver.endUpdate} is called. + */ + handleChange(observable: IObservable, change: TChange): void; + + /** + * Indicates that an update operation has completed. + */ + endUpdate(observable: IObservable): void; +} + +export interface ISettable { + set(value: T, transaction: ITransaction | undefined, change: TChange): void; +} + +export interface ITransaction { + /** + * Calls `Observer.beginUpdate` immediately + * and `Observer.endUpdate` when the transaction is complete. + */ + updateObserver( + observer: IObserver, + observable: IObservable + ): void; +} + +let _derived: typeof derived; +/** + * @internal + * This is to allow splitting files. +*/ +export function _setDerived(derived: typeof _derived) { + _derived = derived; +} + +export abstract class ConvenientObservable implements IObservable { + get TChange(): TChange { return null!; } + + public abstract get(): T; + public abstract addObserver(observer: IObserver): void; + public abstract removeObserver(observer: IObserver): void; + + /** @sealed */ + public read(reader: IReader): T { + reader.subscribeTo(this); + return this.get(); + } + + /** @sealed */ + public map(fn: (value: T) => TNew): IObservable { + return _derived( + () => { + const name = getFunctionName(fn); + return name !== undefined ? name : `${this.debugName} (mapped)`; + }, + (reader) => fn(this.read(reader)) + ); + } + + public abstract get debugName(): string; +} + +export abstract class BaseObservable extends ConvenientObservable { + protected readonly observers = new Set(); + + /** @sealed */ + public addObserver(observer: IObserver): void { + const len = this.observers.size; + this.observers.add(observer); + if (len === 0) { + this.onFirstObserverAdded(); + } + } + + /** @sealed */ + public removeObserver(observer: IObserver): void { + const deleted = this.observers.delete(observer); + if (deleted && this.observers.size === 0) { + this.onLastObserverRemoved(); + } + } + + protected onFirstObserverAdded(): void { } + protected onLastObserverRemoved(): void { } +} + +export function transaction(fn: (tx: ITransaction) => void, getDebugName?: () => string): void { + const tx = new TransactionImpl(fn, getDebugName); + try { + getLogger()?.handleBeginTransaction(tx); + fn(tx); + } finally { + tx.finish(); + getLogger()?.handleEndTransaction(); + } +} + +export function getFunctionName(fn: Function): string | undefined { + const fnSrc = fn.toString(); + // Pattern: /** @description ... */ + const regexp = /\/\*\*\s*@description\s*([^*]*)\*\//; + const match = regexp.exec(fnSrc); + const result = match ? match[1] : undefined; + return result?.trim(); +} + +export class TransactionImpl implements ITransaction { + private updatingObservers: { observer: IObserver; observable: IObservable }[] | null = []; + + constructor(private readonly fn: Function, private readonly _getDebugName?: () => string) { } + + public getDebugName(): string | undefined { + if (this._getDebugName) { + return this._getDebugName(); + } + return getFunctionName(this.fn); + } + + public updateObserver( + observer: IObserver, + observable: IObservable + ): void { + this.updatingObservers!.push({ observer, observable }); + observer.beginUpdate(observable); + } + + public finish(): void { + const updatingObservers = this.updatingObservers!; + // Prevent anyone from updating observers from now on. + this.updatingObservers = null; + for (const { observer, observable } of updatingObservers) { + observer.endUpdate(observable); + } + } +} + +export interface ISettableObservable extends IObservable, ISettable { +} + +export function observableValue(name: string, initialValue: T): ISettableObservable { + return new ObservableValue(name, initialValue); +} + +export class ObservableValue + extends BaseObservable + implements ISettableObservable +{ + private value: T; + + constructor(public readonly debugName: string, initialValue: T) { + super(); + this.value = initialValue; + } + + public get(): T { + return this.value; + } + + public set(value: T, tx: ITransaction | undefined, change: TChange): void { + if (this.value === value) { + return; + } + + if (!tx) { + transaction((tx) => { + this.set(value, tx, change); + }, () => `Setting ${this.debugName}`); + return; + } + + const oldValue = this.value; + this.value = value; + getLogger()?.handleObservableChanged(this, { oldValue, newValue: value, change, didChange: true }); + + for (const observer of this.observers) { + tx.updateObserver(observer, this); + observer.handleChange(this, change); + } + } + + override toString(): string { + return `${this.debugName}: ${this.value}`; + } +} + diff --git a/src/vs/base/common/observableImpl/derived.ts b/src/vs/base/common/observableImpl/derived.ts new file mode 100644 index 00000000000..84a93132f15 --- /dev/null +++ b/src/vs/base/common/observableImpl/derived.ts @@ -0,0 +1,167 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IReader, IObservable, BaseObservable, IObserver, _setDerived } from 'vs/base/common/observableImpl/base'; +import { getLogger } from 'vs/base/common/observableImpl/logging'; + +export function derived(debugName: string | (() => string), computeFn: (reader: IReader) => T): IObservable { + return new Derived(debugName, computeFn); +} + +_setDerived(derived); + +export class Derived extends BaseObservable implements IReader, IObserver { + private hadValue = false; + private hasValue = false; + private value: T | undefined = undefined; + private updateCount = 0; + + private _dependencies = new Set>(); + public get dependencies(): ReadonlySet> { + return this._dependencies; + } + + /** + * Dependencies that have to be removed when {@link runFn} ran through. + */ + private staleDependencies = new Set>(); + + public override get debugName(): string { + return typeof this._debugName === 'function' ? this._debugName() : this._debugName; + } + + constructor( + private readonly _debugName: string | (() => string), + private readonly computeFn: (reader: IReader) => T + ) { + super(); + + getLogger()?.handleDerivedCreated(this); + } + + protected override onLastObserverRemoved(): void { + /** + * We are not tracking changes anymore, thus we have to assume + * that our cache is invalid. + */ + this.hasValue = false; + this.hadValue = false; + this.value = undefined; + for (const d of this._dependencies) { + d.removeObserver(this); + } + this._dependencies.clear(); + } + + public get(): T { + if (this.observers.size === 0) { + // Cache is not valid and don't refresh the cache. + // Observables should not be read in non-reactive contexts. + const result = this.computeFn(this); + // Clear new dependencies + this.onLastObserverRemoved(); + return result; + } + + if (this.updateCount > 0 && this.hasValue) { + // Refresh dependencies + for (const d of this._dependencies) { + // Maybe `.get()` triggers `handleChange`? + d.get(); + if (!this.hasValue) { + // The other dependencies will refresh on demand + break; + } + } + } + + if (!this.hasValue) { + const emptySet = this.staleDependencies; + this.staleDependencies = this._dependencies; + this._dependencies = emptySet; + + const oldValue = this.value; + try { + this.value = this.computeFn(this); + } finally { + // We don't want our observed observables to think that they are (not even temporarily) not being observed. + // Thus, we only unsubscribe from observables that are definitely not read anymore. + for (const o of this.staleDependencies) { + o.removeObserver(this); + } + this.staleDependencies.clear(); + } + + this.hasValue = true; + const didChange = this.hadValue && oldValue !== this.value; + getLogger()?.handleDerivedRecomputed(this, { + oldValue, + newValue: this.value, + change: undefined, + didChange + }); + if (didChange) { + for (const r of this.observers) { + r.handleChange(this, undefined); + } + } + } + return this.value!; + } + + // IObserver Implementation + public beginUpdate(): void { + if (this.updateCount === 0) { + for (const r of this.observers) { + r.beginUpdate(this); + } + } + this.updateCount++; + } + + public handleChange( + _observable: IObservable, + _change: TChange + ): void { + if (this.hasValue) { + this.hadValue = true; + this.hasValue = false; + } + + // Not in transaction: Recompute & inform observers immediately + if (this.updateCount === 0 && this.observers.size > 0) { + this.get(); + } + + // Otherwise, recompute in `endUpdate` or on demand. + } + + public endUpdate(): void { + this.updateCount--; + if (this.updateCount === 0) { + if (this.observers.size > 0) { + // Propagate invalidation + this.get(); + } + + for (const r of this.observers) { + r.endUpdate(this); + } + } + } + + // IReader Implementation + public subscribeTo(observable: IObservable) { + this._dependencies.add(observable); + // We are already added as observer for stale dependencies. + if (!this.staleDependencies.delete(observable)) { + observable.addObserver(this); + } + } + + override toString(): string { + return `LazyDerived<${this.debugName}>`; + } +} diff --git a/src/vs/base/common/observableImpl/logging.ts b/src/vs/base/common/observableImpl/logging.ts new file mode 100644 index 00000000000..0c221d0c700 --- /dev/null +++ b/src/vs/base/common/observableImpl/logging.ts @@ -0,0 +1,312 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { AutorunObserver } from 'vs/base/common/observableImpl/autorun'; +import { IObservable, ObservableValue, TransactionImpl } from 'vs/base/common/observableImpl/base'; +import { Derived } from 'vs/base/common/observableImpl/derived'; +import { FromEventObservable } from 'vs/base/common/observableImpl/utils'; + +let globalObservableLogger: IObservableLogger | undefined; + +export function setLogger(logger: IObservableLogger): void { + globalObservableLogger = logger; +} + +export function getLogger(): IObservableLogger | undefined { + return globalObservableLogger; +} + +interface IChangeInformation { + oldValue: unknown; + newValue: unknown; + change: unknown; + didChange: boolean; +} + +export interface IObservableLogger { + handleObservableChanged(observable: ObservableValue, info: IChangeInformation): void; + handleFromEventObservableTriggered(observable: FromEventObservable, info: IChangeInformation): void; + + handleAutorunCreated(autorun: AutorunObserver): void; + handleAutorunTriggered(autorun: AutorunObserver): void; + + handleDerivedCreated(observable: Derived): void; + handleDerivedRecomputed(observable: Derived, info: IChangeInformation): void; + + handleBeginTransaction(transaction: TransactionImpl): void; + handleEndTransaction(): void; +} + +export class ConsoleObservableLogger implements IObservableLogger { + private indentation = 0; + + private textToConsoleArgs(text: ConsoleText): unknown[] { + return consoleTextToArgs([ + normalText(repeat('| ', this.indentation)), + text, + ]); + } + + private formatInfo(info: IChangeInformation): ConsoleText[] { + return info.didChange + ? [ + normalText(` `), + styled(formatValue(info.oldValue, 70), { + color: 'red', + strikeThrough: true, + }), + normalText(` `), + styled(formatValue(info.newValue, 60), { + color: 'green', + }), + ] + : [normalText(` (unchanged)`)]; + } + + handleObservableChanged(observable: IObservable, info: IChangeInformation): void { + console.log(...this.textToConsoleArgs([ + formatKind('observable value changed'), + styled(observable.debugName, { color: 'BlueViolet' }), + ...this.formatInfo(info), + ])); + } + + private readonly changedObservablesSets = new WeakMap>>(); + + formatChanges(changes: Set>): ConsoleText | undefined { + if (changes.size === 0) { + return undefined; + } + return styled( + ' (changed deps: ' + + [...changes].map((o) => o.debugName).join(', ') + + ')', + { color: 'gray' } + ); + } + + handleDerivedCreated(derived: Derived): void { + const existingHandleChange = derived.handleChange; + this.changedObservablesSets.set(derived, new Set()); + derived.handleChange = (observable, change) => { + this.changedObservablesSets.get(derived)!.add(observable); + return existingHandleChange.apply(derived, [observable, change]); + }; + } + + handleDerivedRecomputed(derived: Derived, info: IChangeInformation): void { + const changedObservables = this.changedObservablesSets.get(derived)!; + console.log(...this.textToConsoleArgs([ + formatKind('derived recomputed'), + styled(derived.debugName, { color: 'BlueViolet' }), + ...this.formatInfo(info), + this.formatChanges(changedObservables) + ])); + changedObservables.clear(); + } + + handleFromEventObservableTriggered(observable: FromEventObservable, info: IChangeInformation): void { + console.log(...this.textToConsoleArgs([ + formatKind('observable from event triggered'), + styled(observable.debugName, { color: 'BlueViolet' }), + ...this.formatInfo(info), + ])); + } + + handleAutorunCreated(autorun: AutorunObserver): void { + const existingHandleChange = autorun.handleChange; + this.changedObservablesSets.set(autorun, new Set()); + autorun.handleChange = (observable, change) => { + this.changedObservablesSets.get(autorun)!.add(observable); + return existingHandleChange.apply(autorun, [observable, change]); + }; + } + + handleAutorunTriggered(autorun: AutorunObserver): void { + const changedObservables = this.changedObservablesSets.get(autorun)!; + console.log(...this.textToConsoleArgs([ + formatKind('autorun'), + styled(autorun.debugName, { color: 'BlueViolet' }), + this.formatChanges(changedObservables) + ])); + changedObservables.clear(); + } + + handleBeginTransaction(transaction: TransactionImpl): void { + let transactionName = transaction.getDebugName(); + if (transactionName === undefined) { + transactionName = ''; + } + console.log(...this.textToConsoleArgs([ + formatKind('transaction'), + styled(transactionName, { color: 'BlueViolet' }), + ])); + this.indentation++; + } + + handleEndTransaction(): void { + this.indentation--; + } +} + +type ConsoleText = + | (ConsoleText | undefined)[] + | { text: string; style: string; data?: Record } + | { data: Record }; + +function consoleTextToArgs(text: ConsoleText): unknown[] { + const styles = new Array(); + const initial = {}; + const data = initial; + let firstArg = ''; + + function process(t: ConsoleText): void { + if ('length' in t) { + for (const item of t) { + if (item) { + process(item); + } + } + } else if ('text' in t) { + firstArg += `%c${t.text}`; + styles.push(t.style); + if (t.data) { + Object.assign(data, t.data); + } + } else if ('data' in t) { + Object.assign(data, t.data); + } + } + + process(text); + + const result = [firstArg, ...styles]; + if (Object.keys(data).length > 0) { + result.push(data); + } + + return result; +} + +function normalText(text: string): ConsoleText { + return styled(text, { color: 'black' }); +} + +function formatKind(kind: string): ConsoleText { + return styled(padStr(`${kind}: `, 10), { color: 'black', bold: true }); +} + +function styled( + text: string, + options: { color: string; strikeThrough?: boolean; bold?: boolean } = { + color: 'black', + } +): ConsoleText { + function objToCss(styleObj: Record): string { + return Object.entries(styleObj).reduce( + (styleString, [propName, propValue]) => { + return `${styleString}${propName}:${propValue};`; + }, + '' + ); + } + + const style: Record = { + color: options.color, + }; + if (options.strikeThrough) { + style['text-decoration'] = 'line-through'; + } + if (options.bold) { + style['font-weight'] = 'bold'; + } + + return { + text, + style: objToCss(style), + }; +} + +function formatValue(value: unknown, availableLen: number): string { + switch (typeof value) { + case 'number': + return '' + value; + case 'string': + if (value.length + 2 <= availableLen) { + return `"${value}"`; + } + return `"${value.substr(0, availableLen - 7)}"+...`; + + case 'boolean': + return value ? 'true' : 'false'; + case 'undefined': + return 'undefined'; + case 'object': + if (value === null) { + return 'null'; + } + if (Array.isArray(value)) { + return formatArray(value, availableLen); + } + return formatObject(value, availableLen); + case 'symbol': + return value.toString(); + case 'function': + return `[[Function${value.name ? ' ' + value.name : ''}]]`; + default: + return '' + value; + } +} + +function formatArray(value: unknown[], availableLen: number): string { + let result = '[ '; + let first = true; + for (const val of value) { + if (!first) { + result += ', '; + } + if (result.length - 5 > availableLen) { + result += '...'; + break; + } + first = false; + result += `${formatValue(val, availableLen - result.length)}`; + } + result += ' ]'; + return result; +} + +function formatObject(value: object, availableLen: number): string { + let result = '{ '; + let first = true; + for (const [key, val] of Object.entries(value)) { + if (!first) { + result += ', '; + } + if (result.length - 5 > availableLen) { + result += '...'; + break; + } + first = false; + result += `${key}: ${formatValue(val, availableLen - result.length)}`; + } + result += ' }'; + return result; +} + +function repeat(str: string, count: number): string { + let result = ''; + for (let i = 1; i <= count; i++) { + result += str; + } + return result; +} + +function padStr(str: string, length: number): string { + while (str.length < length) { + str += ' '; + } + return str; +} diff --git a/src/vs/base/common/observableImpl/utils.ts b/src/vs/base/common/observableImpl/utils.ts new file mode 100644 index 00000000000..0b07d089b83 --- /dev/null +++ b/src/vs/base/common/observableImpl/utils.ts @@ -0,0 +1,281 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { autorun } from 'vs/base/common/observableImpl/autorun'; +import { IObservable, BaseObservable, transaction, IReader, ITransaction, ConvenientObservable, IObserver, observableValue, getFunctionName } from 'vs/base/common/observableImpl/base'; +import { derived } from 'vs/base/common/observableImpl/derived'; +import { Event } from 'vs/base/common/event'; +import { getLogger } from 'vs/base/common/observableImpl/logging'; + +export function constObservable(value: T): IObservable { + return new ConstObservable(value); +} + +class ConstObservable extends ConvenientObservable { + constructor(private readonly value: T) { + super(); + } + + public override get debugName(): string { + return this.toString(); + } + + public get(): T { + return this.value; + } + public addObserver(observer: IObserver): void { + // NO OP + } + public removeObserver(observer: IObserver): void { + // NO OP + } + + override toString(): string { + return `Const: ${this.value}`; + } +} + + +export function observableFromPromise(promise: Promise): IObservable<{ value?: T }> { + const observable = observableValue<{ value?: T }>('promiseValue', {}); + promise.then((value) => { + observable.set({ value }, undefined); + }); + return observable; +} + +export function waitForState(observable: IObservable, predicate: (state: T) => state is TState): Promise; +export function waitForState(observable: IObservable, predicate: (state: T) => boolean): Promise; +export function waitForState(observable: IObservable, predicate: (state: T) => boolean): Promise { + return new Promise(resolve => { + const d = autorun('waitForState', reader => { + const currentState = observable.read(reader); + if (predicate(currentState)) { + d.dispose(); + resolve(currentState); + } + }); + }); +} + +export function observableFromEvent( + event: Event, + getValue: (args: TArgs | undefined) => T +): IObservable { + return new FromEventObservable(event, getValue); +} + +export class FromEventObservable extends BaseObservable { + private value: T | undefined; + private hasValue = false; + private subscription: IDisposable | undefined; + + constructor( + private readonly event: Event, + private readonly getValue: (args: TArgs | undefined) => T + ) { + super(); + } + + private getDebugName(): string | undefined { + return getFunctionName(this.getValue); + } + + public get debugName(): string { + const name = this.getDebugName(); + return 'From Event' + (name ? `: ${name}` : ''); + } + + protected override onFirstObserverAdded(): void { + this.subscription = this.event(this.handleEvent); + } + + private readonly handleEvent = (args: TArgs | undefined) => { + const newValue = this.getValue(args); + + const didChange = this.value !== newValue; + + getLogger()?.handleFromEventObservableTriggered(this, { oldValue: this.value, newValue, change: undefined, didChange }); + + if (didChange) { + this.value = newValue; + + if (this.hasValue) { + transaction( + (tx) => { + for (const o of this.observers) { + tx.updateObserver(o, this); + o.handleChange(this, undefined); + } + }, + () => { + const name = this.getDebugName(); + return 'Event fired' + (name ? `: ${name}` : ''); + } + ); + } + this.hasValue = true; + } + }; + + protected override onLastObserverRemoved(): void { + this.subscription!.dispose(); + this.subscription = undefined; + this.hasValue = false; + this.value = undefined; + } + + public get(): T { + if (this.subscription) { + if (!this.hasValue) { + this.handleEvent(undefined); + } + return this.value!; + } else { + // no cache, as there are no subscribers to keep it updated + return this.getValue(undefined); + } + } +} + +export namespace observableFromEvent { + export const Observer = FromEventObservable; +} + +export function observableSignalFromEvent( + debugName: string, + event: Event +): IObservable { + return new FromEventObservableSignal(debugName, event); +} + +class FromEventObservableSignal extends BaseObservable { + private subscription: IDisposable | undefined; + + constructor( + public readonly debugName: string, + private readonly event: Event, + ) { + super(); + } + + protected override onFirstObserverAdded(): void { + this.subscription = this.event(this.handleEvent); + } + + private readonly handleEvent = () => { + transaction( + (tx) => { + for (const o of this.observers) { + tx.updateObserver(o, this); + o.handleChange(this, undefined); + } + }, + () => this.debugName + ); + }; + + protected override onLastObserverRemoved(): void { + this.subscription!.dispose(); + this.subscription = undefined; + } + + public override get(): void { + // NO OP + } +} + +export function debouncedObservable(observable: IObservable, debounceMs: number, disposableStore: DisposableStore): IObservable { + const debouncedObservable = observableValue('debounced', undefined); + + let timeout: any = undefined; + + disposableStore.add(autorun('debounce', reader => { + const value = observable.read(reader); + + if (timeout) { + clearTimeout(timeout); + } + timeout = setTimeout(() => { + transaction(tx => { + debouncedObservable.set(value, tx); + }); + }, debounceMs); + + })); + + return debouncedObservable; +} + +export function wasEventTriggeredRecently(event: Event, timeoutMs: number, disposableStore: DisposableStore): IObservable { + const observable = observableValue('triggeredRecently', false); + + let timeout: any = undefined; + + disposableStore.add(event(() => { + observable.set(true, undefined); + + if (timeout) { + clearTimeout(timeout); + } + timeout = setTimeout(() => { + observable.set(false, undefined); + }, timeoutMs); + })); + + return observable; +} + +/** + * This ensures the observable is kept up-to-date. + * This is useful when the observables `get` method is used. +*/ +export function keepAlive(observable: IObservable): IDisposable { + const o = new KeepAliveObserver(); + observable.addObserver(o); + return toDisposable(() => { + observable.removeObserver(o); + }); +} + +class KeepAliveObserver implements IObserver { + beginUpdate(observable: IObservable): void { + // NO OP + } + + handleChange(observable: IObservable, change: TChange): void { + // NO OP + } + + endUpdate(observable: IObservable): void { + // NO OP + } +} + +export function derivedObservableWithCache(name: string, computeFn: (reader: IReader, lastValue: T | undefined) => T): IObservable { + let lastValue: T | undefined = undefined; + const observable = derived(name, reader => { + lastValue = computeFn(reader, lastValue); + return lastValue; + }); + return observable; +} + +export function derivedObservableWithWritableCache(name: string, computeFn: (reader: IReader, lastValue: T | undefined) => T): IObservable & { clearCache(transaction: ITransaction): void } { + let lastValue: T | undefined = undefined; + const counter = observableValue('derivedObservableWithWritableCache.counter', 0); + const observable = derived(name, reader => { + counter.read(reader); + lastValue = computeFn(reader, lastValue); + return lastValue; + }); + return Object.assign(observable, { + clearCache: (transaction: ITransaction) => { + lastValue = undefined; + counter.set(counter.get() + 1, transaction); + }, + }); +} diff --git a/src/vs/base/test/common/observable.test.ts b/src/vs/base/test/common/observable.test.ts new file mode 100644 index 00000000000..8adba720180 --- /dev/null +++ b/src/vs/base/test/common/observable.test.ts @@ -0,0 +1,468 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { Emitter } from 'vs/base/common/event'; +import { autorun, derived, IObserver, ITransaction, observableFromEvent, observableValue, transaction } from 'vs/base/common/observable'; +import { BaseObservable } from 'vs/base/common/observableImpl/base'; + +suite('observable integration', () => { + test('basic observable + autorun', () => { + const log = new Log(); + const observable = observableValue('MyObservableValue', 0); + + autorun('MyAutorun', (reader) => { + log.log(`value: ${observable.read(reader)}`); + }); + assert.deepStrictEqual(log.getAndClearEntries(), ['value: 0']); + + observable.set(1, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), ['value: 1']); + + observable.set(1, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + transaction((tx) => { + observable.set(2, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + observable.set(3, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + }); + + assert.deepStrictEqual(log.getAndClearEntries(), ['value: 3']); + }); + + test('basic computed + autorun', () => { + const log = new Log(); + const observable1 = observableValue('MyObservableValue1', 0); + const observable2 = observableValue('MyObservableValue2', 0); + + const computed = derived('computed', (reader) => { + const value1 = observable1.read(reader); + const value2 = observable2.read(reader); + const sum = value1 + value2; + log.log(`recompute: ${value1} + ${value2} = ${sum}`); + return sum; + }); + + autorun('MyAutorun', (reader) => { + log.log(`value: ${computed.read(reader)}`); + }); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute: 0 + 0 = 0', + 'value: 0', + ]); + + observable1.set(1, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute: 1 + 0 = 1', + 'value: 1', + ]); + + observable2.set(1, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute: 1 + 1 = 2', + 'value: 2', + ]); + + transaction((tx) => { + observable1.set(5, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + observable2.set(5, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + }); + + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute: 5 + 5 = 10', + 'value: 10', + ]); + + transaction((tx) => { + observable1.set(6, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + observable2.set(4, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + }); + + assert.deepStrictEqual(log.getAndClearEntries(), ['recompute: 6 + 4 = 10']); + }); + + test('read during transaction', () => { + const log = new Log(); + const observable1 = observableValue('MyObservableValue1', 0); + const observable2 = observableValue('MyObservableValue2', 0); + + const computed = derived('computed', (reader) => { + const value1 = observable1.read(reader); + const value2 = observable2.read(reader); + const sum = value1 + value2; + log.log(`recompute: ${value1} + ${value2} = ${sum}`); + return sum; + }); + + autorun('MyAutorun', (reader) => { + log.log(`value: ${computed.read(reader)}`); + }); + + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute: 0 + 0 = 0', + 'value: 0', + ]); + + log.log(`computed is ${computed.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), ['computed is 0']); + + transaction((tx) => { + observable1.set(-1, tx); + log.log(`computed is ${computed.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute: -1 + 0 = -1', + 'computed is -1', + ]); + + log.log(`computed is ${computed.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), ['computed is -1']); + + observable2.set(1, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + }); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute: -1 + 1 = 0', + 'value: 0', + ]); + }); + + test('topological order', () => { + const log = new Log(); + const observable1 = observableValue('MyObservableValue1', 0); + const observable2 = observableValue('MyObservableValue2', 0); + + const computed1 = derived('computed1', (reader) => { + const value1 = observable1.read(reader); + const value2 = observable2.read(reader); + const sum = value1 + value2; + log.log(`recompute1: ${value1} + ${value2} = ${sum}`); + return sum; + }); + + const computed2 = derived('computed2', (reader) => { + const value1 = computed1.read(reader); + const value2 = observable1.read(reader); + const value3 = observable2.read(reader); + const sum = value1 + value2 + value3; + log.log(`recompute2: ${value1} + ${value2} + ${value3} = ${sum}`); + return sum; + }); + + const computed3 = derived('computed3', (reader) => { + const value1 = computed2.read(reader); + const value2 = observable1.read(reader); + const value3 = observable2.read(reader); + const sum = value1 + value2 + value3; + log.log(`recompute3: ${value1} + ${value2} + ${value3} = ${sum}`); + return sum; + }); + + autorun('MyAutorun', (reader) => { + log.log(`value: ${computed3.read(reader)}`); + }); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute1: 0 + 0 = 0', + 'recompute2: 0 + 0 + 0 = 0', + 'recompute3: 0 + 0 + 0 = 0', + 'value: 0', + ]); + + observable1.set(1, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute1: 1 + 0 = 1', + 'recompute2: 1 + 1 + 0 = 2', + 'recompute3: 2 + 1 + 0 = 3', + 'value: 3', + ]); + + transaction((tx) => { + observable1.set(2, tx); + log.log(`computed2: ${computed2.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute1: 2 + 0 = 2', + 'recompute2: 2 + 2 + 0 = 4', + 'computed2: 4', + ]); + + observable1.set(3, tx); + log.log(`computed2: ${computed2.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute1: 3 + 0 = 3', + 'recompute2: 3 + 3 + 0 = 6', + 'computed2: 6', + ]); + }); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute3: 6 + 3 + 0 = 9', + 'value: 9', + ]); + }); + + test('transaction from autorun', () => { + const log = new Log(); + + const observable1 = observableValue('MyObservableValue1', 0); + const observable2 = observableValue('MyObservableValue2', 0); + + const computed = derived('computed', (reader) => { + const value1 = observable1.read(reader); + const value2 = observable2.read(reader); + const sum = value1 + value2; + log.log(`recompute: ${value1} + ${value2} = ${sum}`); + return sum; + }); + + autorun('autorun', (reader) => { + log.log(`value: ${computed.read(reader)}`); + transaction(tx => { + + }); + + }); + + + }); + + test('from event', () => { + const log = new Log(); + + let value = 0; + const eventEmitter = new Emitter(); + + let id = 0; + const observable = observableFromEvent( + (handler) => { + const curId = id++; + log.log(`subscribed handler ${curId}`); + const disposable = eventEmitter.event(handler); + + return { + dispose: () => { + log.log(`unsubscribed handler ${curId}`); + disposable.dispose(); + }, + }; + }, + () => { + log.log(`compute value ${value}`); + return value; + } + ); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + log.log(`get value: ${observable.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'compute value 0', + 'get value: 0', + ]); + + log.log(`get value: ${observable.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'compute value 0', + 'get value: 0', + ]); + + const shouldReadObservable = observableValue('shouldReadObservable', true); + + const autorunDisposable = autorun('MyAutorun', (reader) => { + if (shouldReadObservable.read(reader)) { + observable.read(reader); + log.log( + `autorun, should read: true, value: ${observable.read(reader)}` + ); + } else { + log.log(`autorun, should read: false`); + } + }); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'subscribed handler 0', + 'compute value 0', + 'autorun, should read: true, value: 0', + ]); + + log.log(`get value: ${observable.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), ['get value: 0']); + + value = 1; + eventEmitter.fire(); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'compute value 1', + 'autorun, should read: true, value: 1', + ]); + + shouldReadObservable.set(false, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'autorun, should read: false', + 'unsubscribed handler 0', + ]); + + shouldReadObservable.set(true, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'subscribed handler 1', + 'compute value 1', + 'autorun, should read: true, value: 1', + ]); + + autorunDisposable.dispose(); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'unsubscribed handler 1', + ]); + }); + + test('get without observers', () => { + // Maybe this scenario should not be supported. + + const log = new Log(); + const observable1 = observableValue('MyObservableValue1', 0); + const computed1 = derived('computed', (reader) => { + const value1 = observable1.read(reader); + const result = value1 % 3; + log.log(`recompute1: ${value1} % 3 = ${result}`); + return result; + }); + const computed2 = derived('computed', (reader) => { + const value1 = computed1.read(reader); + + const result = value1 * 2; + log.log(`recompute2: ${value1} * 2 = ${result}`); + return result; + }); + const computed3 = derived('computed', (reader) => { + const value1 = computed1.read(reader); + + const result = value1 * 3; + log.log(`recompute3: ${value1} * 3 = ${result}`); + return result; + }); + const computedSum = derived('computed', (reader) => { + const value1 = computed2.read(reader); + const value2 = computed3.read(reader); + + const result = value1 + value2; + log.log(`recompute4: ${value1} + ${value2} = ${result}`); + return result; + }); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + observable1.set(1, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + log.log(`value: ${computedSum.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute1: 1 % 3 = 1', + 'recompute2: 1 * 2 = 2', + 'recompute3: 1 * 3 = 3', + 'recompute4: 2 + 3 = 5', + 'value: 5', + ]); + + log.log(`value: ${computedSum.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute1: 1 % 3 = 1', + 'recompute2: 1 * 2 = 2', + 'recompute3: 1 * 3 = 3', + 'recompute4: 2 + 3 = 5', + 'value: 5', + ]); + }); +}); + +suite('observable details', () => { + test('1', () => { + const log = new Log(); + + class TrackedObservableValue extends BaseObservable { + private value: T; + + constructor(initialValue: T) { + super(); + this.value = initialValue; + } + + readonly debugName = 'TrackedObservableValue'; + + public override addObserver(observer: IObserver): void { + log.log(`observable.addObserver ${observer.toString()}`); + super.addObserver(observer); + } + + public override removeObserver(observer: IObserver): void { + log.log(`observable.removeObserver ${observer.toString()}`); + super.removeObserver(observer); + } + + public get(): T { + log.log('observable.get'); + return this.value; + } + + public set(value: T, tx: ITransaction): void { + log.log(`observable.set (value ${value})`); + + if (this.value === value) { + return; + } + this.value = value; + for (const observer of this.observers) { + tx.updateObserver(observer, this); + observer.handleChange(this, undefined); + } + } + } + + const shouldReadObservable = observableValue('shouldReadObservable', true); + const observable = new TrackedObservableValue(0); + const computed = derived('test', reader => { + if (shouldReadObservable.read(reader)) { + return observable.read(reader) * 2; + } + return 1; + }); + autorun('test', reader => { + const value = computed.read(reader); + log.log(`autorun: ${value}`); + }); + + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'observable.addObserver LazyDerived', + 'observable.get', + 'autorun: 0', + ]); + + transaction(tx => { + observable.set(1, tx); + assert.deepStrictEqual(log.getAndClearEntries(), (["observable.set (value 1)"])); + + shouldReadObservable.set(false, tx); + assert.deepStrictEqual(log.getAndClearEntries(), ([])); + + computed.get(); + assert.deepStrictEqual(log.getAndClearEntries(), (["observable.removeObserver LazyDerived"])); + }); + assert.deepStrictEqual(log.getAndClearEntries(), (["autorun: 1"])); + }); +}); + +class Log { + private readonly entries: string[] = []; + public log(message: string): void { + this.entries.push(message); + } + + public getAndClearEntries(): string[] { + const entries = [...this.entries]; + this.entries.length = 0; + return entries; + } +} diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts index 3c348baa40a..edbdd468675 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts @@ -96,6 +96,7 @@ export class AudioCueLineFeatureContribution const curLineNumber = observableFromEvent( editor.onDidChangeCursorPosition, (args) => { + /** @description editor.onDidChangeCursorPosition (caused by user) */ if ( args && args.reason !== CursorChangeReason.Explicit && @@ -193,7 +194,7 @@ class MarkerLineFeature implements LineFeature { Event.filter(this.markerService.onMarkerChanged, (changedUris) => changedUris.some((u) => u.toString() === model.uri.toString()) ), - () => ({ + () => /** @description this.markerService.onMarkerChanged */({ isPresent: (lineNumber) => { const hasMarker = this.markerService .read({ resource: model.uri }) @@ -245,7 +246,7 @@ class BreakpointLineFeature implements LineFeature { getObservableState(editor: ICodeEditor, model: ITextModel): IObservable { return observableFromEvent( this.debugService.getModel().onDidChangeBreakpoints, - () => ({ + () => /** @description debugService.getModel().onDidChangeBreakpoints */({ isPresent: (lineNumber) => { const breakpoints = this.debugService .getModel() @@ -271,12 +272,12 @@ class InlineCompletionLineFeature implements LineFeature { const activeGhostText = observableFromEvent( ghostTextController.onActiveModelDidChange, - () => ghostTextController.activeModel + () => /** @description ghostTextController.onActiveModelDidChange */ ghostTextController.activeModel ).map((activeModel) => ( activeModel ? observableFromEvent( activeModel.inlineCompletionsModel.onDidChange, - () => activeModel.inlineCompletionsModel.ghostText + () => /** @description activeModel.inlineCompletionsModel.onDidChange */ activeModel.inlineCompletionsModel.ghostText ) : undefined )); diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCueService.ts b/src/vs/workbench/contrib/audioCues/browser/audioCueService.ts index dcac8bf037b..f313933a8ec 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCueService.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCueService.ts @@ -9,7 +9,7 @@ import { FileAccess } from 'vs/base/common/network'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { observableFromEvent, IObservable, LazyDerived } from 'vs/workbench/contrib/audioCues/browser/observable'; +import { observableFromEvent, IObservable, derivedObservable } from 'vs/workbench/contrib/audioCues/browser/observable'; import { Event } from 'vs/base/common/event'; import { localize } from 'vs/nls'; @@ -29,7 +29,7 @@ export class AudioCueService extends Disposable implements IAudioCueService { private readonly screenReaderAttached = observableFromEvent( this.accessibilityService.onDidChangeScreenReaderOptimized, - () => this.accessibilityService.isScreenReaderOptimized() + () => /** @description accessibilityService.onDidChangeScreenReaderOptimized */ this.accessibilityService.isScreenReaderOptimized() ); constructor( @@ -85,7 +85,7 @@ export class AudioCueService extends Disposable implements IAudioCueService { Event.filter(this.configurationService.onDidChangeConfiguration, (e) => e.affectsConfiguration('audioCues.enabled') ), - () => this.configurationService.getValue<'on' | 'off' | 'auto'>('audioCues.enabled') + () => /** @description config: audioCues.enabled */ this.configurationService.getValue<'on' | 'off' | 'auto'>('audioCues.enabled') ); private readonly isEnabledCache = new Cache((cue: AudioCue) => { @@ -95,7 +95,7 @@ export class AudioCueService extends Disposable implements IAudioCueService { ), () => this.configurationService.getValue<'on' | 'off' | 'auto'>(cue.settingsKey) ); - return new LazyDerived(reader => { + return derivedObservable('audio cue enabled', reader => { const setting = settingObservable.read(reader); if ( setting === 'on' || @@ -113,7 +113,7 @@ export class AudioCueService extends Disposable implements IAudioCueService { } return false; - }, 'audio cue enabled'); + }); }); public isEnabled(cue: AudioCue): IObservable { diff --git a/src/vs/workbench/contrib/audioCues/browser/observable.ts b/src/vs/workbench/contrib/audioCues/browser/observable.ts index 2ad62412411..37d2f8a97c8 100644 --- a/src/vs/workbench/contrib/audioCues/browser/observable.ts +++ b/src/vs/workbench/contrib/audioCues/browser/observable.ts @@ -3,688 +3,38 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Event } from 'vs/base/common/event'; -import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import * as observable from 'vs/base/common/observable'; +export { + observableFromEvent, + autorunWithStore, + IObservable, + transaction, + ITransaction, + autorunDelta, + constObservable, + observableFromPromise, + wasEventTriggeredRecently, + debouncedObservable, + autorunHandleChanges, + waitForState, + keepAlive, + IReader, + derivedObservableWithCache, + derivedObservableWithWritableCache, +} from 'vs/base/common/observable'; +import * as observableValue from 'vs/base/common/observableImpl/base'; -export interface IObservable { - _change: TChange; - - /** - * Reads the current value. - * - * This causes a recomputation if needed. - * Calling this method forces changes to propagate to observers during update operations. - * Must not be called from {@link IObserver.handleChange}. - */ - get(): T; - - /** - * Registers an observer. - * - * Calls {@link IObserver.handleChange} immediately after a change is noticed. - * Might happen while someone calls {@link IObservable.get} or {@link IObservable.read}. - */ - subscribe(observer: IObserver): void; - unsubscribe(observer: IObserver): void; - - /** - * Calls {@link IObservable.get} and then {@link IReader.handleBeforeReadObservable}. - */ - read(reader: IReader): T; - - map(fn: (value: T) => TNew): IObservable; +export function autorun(fn: (reader: observable.IReader) => void, name: string): IDisposable { + return observable.autorun(name, fn); } -export interface IReader { - /** - * Reports an observable that was read. - * - * Is called by `Observable.read`. - */ - handleBeforeReadObservable(observable: IObservable): void; -} - -export interface IObserver { - /** - * Indicates that an update operation is about to begin. - * - * During an update, invariants might not hold for subscribed observables and - * change events might be delayed. - * However, all changes must be reported before all update operations are over. - */ - beginUpdate(observable: IObservable): void; - - /** - * Is called by a subscribed observable immediately after it notices a change. - * - * When {@link IObservable.get} returns and no change has been reported, - * there has been no change for that observable. - * - * Implementations must not call into other observables! - * The change should be processed when {@link IObserver.endUpdate} is called. - */ - handleChange(observable: IObservable, change: TChange): void; - - /** - * Indicates that an update operation has completed. - */ - endUpdate(observable: IObservable): void; -} - -export interface ISettable { - set(value: T, transaction: ITransaction | undefined, change: TChange): void; -} - -export interface ITransaction { - /** - * Calls `Observer.beginUpdate` immediately - * and `Observer.endUpdate` when the transaction is complete. - */ - updateObserver( - observer: IObserver, - observable: IObservable - ): void; -} - -// === Base === -export abstract class ConvenientObservable implements IObservable { - get _change(): TChange { return null!; } - - public abstract get(): T; - public abstract subscribe(observer: IObserver): void; - public abstract unsubscribe(observer: IObserver): void; - - public read(reader: IReader): T { - reader.handleBeforeReadObservable(this); - return this.get(); - } - - public map(fn: (value: T) => TNew): IObservable { - return new LazyDerived((reader) => fn(this.read(reader)), '(mapped)'); +export class ObservableValue extends observableValue.ObservableValue { + constructor(initialValue: T, name: string) { + super(name, initialValue); } } -export abstract class BaseObservable extends ConvenientObservable { - protected readonly observers = new Set(); - - public subscribe(observer: IObserver): void { - const len = this.observers.size; - this.observers.add(observer); - if (len === 0) { - this.onFirstObserverSubscribed(); - } - } - - public unsubscribe(observer: IObserver): void { - const deleted = this.observers.delete(observer); - if (deleted && this.observers.size === 0) { - this.onLastObserverUnsubscribed(); - } - } - - protected onFirstObserverSubscribed(): void { } - protected onLastObserverUnsubscribed(): void { } -} - -export function transaction(fn: (tx: ITransaction) => void) { - const tx = new TransactionImpl(); - try { - fn(tx); - } finally { - tx.finish(); - } -} - -class TransactionImpl implements ITransaction { - private readonly finishActions = new Array<() => void>(); - - public updateObserver( - observer: IObserver, - observable: IObservable - ): void { - this.finishActions.push(function () { - observer.endUpdate(observable); - }); - observer.beginUpdate(observable); - } - - public finish(): void { - for (const action of this.finishActions) { - action(); - } - } -} - -export class ObservableValue - extends BaseObservable - implements ISettable -{ - private value: T; - - constructor(initialValue: T, public readonly name: string) { - super(); - this.value = initialValue; - } - - public get(): T { - return this.value; - } - - public set(value: T, tx: ITransaction | undefined, change: TChange): void { - if (this.value === value) { - return; - } - - if (!tx) { - transaction((tx) => { - this.set(value, tx, change); - }); - return; - } - - this.value = value; - - for (const observer of this.observers) { - tx.updateObserver(observer, this); - observer.handleChange(this, change); - } - } -} - -export function constObservable(value: T): IObservable { - return new ConstObservable(value); -} - -class ConstObservable extends ConvenientObservable { - constructor(private readonly value: T) { - super(); - } - - public get(): T { - return this.value; - } - public subscribe(observer: IObserver): void { - // NO OP - } - public unsubscribe(observer: IObserver): void { - // NO OP - } -} - -// == autorun == -export function autorun(fn: (reader: IReader) => void, name: string): IDisposable { - return new AutorunObserver(fn, name, undefined); -} - -interface IChangeContext { - readonly changedObservable: IObservable; - readonly change: unknown; - - didChange(observable: IObservable): this is { change: TChange }; -} - -export function autorunHandleChanges( - name: string, - options: { - /** - * Returns if this change should cause a re-run of the autorun. - */ - handleChange: (context: IChangeContext) => boolean; - }, - fn: (reader: IReader) => void -): IDisposable { - return new AutorunObserver(fn, name, options.handleChange); -} - -export function autorunWithStore( - fn: (reader: IReader, store: DisposableStore) => void, - name: string -): IDisposable { - const store = new DisposableStore(); - const disposable = autorun( - reader => { - store.clear(); - fn(reader, store); - }, - name - ); - return toDisposable(() => { - disposable.dispose(); - store.dispose(); - }); -} - -export class AutorunObserver implements IObserver, IReader, IDisposable { - public needsToRun = true; - private updateCount = 0; - - /** - * The actual dependencies. - */ - private _dependencies = new Set>(); - public get dependencies() { - return this._dependencies; - } - - /** - * Dependencies that have to be removed when {@link runFn} ran through. - */ - private staleDependencies = new Set>(); - - constructor( - private readonly runFn: (reader: IReader) => void, - public readonly name: string, - private readonly _handleChange: ((context: IChangeContext) => boolean) | undefined - ) { - this.runIfNeeded(); - } - - public handleBeforeReadObservable(observable: IObservable) { - this._dependencies.add(observable); - if (!this.staleDependencies.delete(observable)) { - observable.subscribe(this); - } - } - - public handleChange(observable: IObservable, change: TChange): void { - const shouldReact = this._handleChange ? this._handleChange({ - changedObservable: observable, - change, - didChange: o => o === observable as any, - }) : true; - this.needsToRun = this.needsToRun || shouldReact; - - if (this.updateCount === 0) { - this.runIfNeeded(); - } - } - - public beginUpdate() { - this.updateCount++; - } - - public endUpdate() { - this.updateCount--; - if (this.updateCount === 0) { - this.runIfNeeded(); - } - } - - private runIfNeeded(): void { - if (!this.needsToRun) { - return; - } - // Assert: this.staleDependencies is an empty set. - const emptySet = this.staleDependencies; - this.staleDependencies = this._dependencies; - this._dependencies = emptySet; - - this.needsToRun = false; - - try { - this.runFn(this); - } finally { - // We don't want our observed observables to think that they are (not even temporarily) not being observed. - // Thus, we only unsubscribe from observables that are definitely not read anymore. - for (const o of this.staleDependencies) { - o.unsubscribe(this); - } - this.staleDependencies.clear(); - } - } - - public dispose() { - for (const o of this._dependencies) { - o.unsubscribe(this); - } - this._dependencies.clear(); - } -} - -export namespace autorun { - export const Observer = AutorunObserver; -} -export function autorunDelta( - name: string, - observable: IObservable, - handler: (args: { lastValue: T | undefined; newValue: T }) => void -): IDisposable { - let _lastValue: T | undefined; - return autorun((reader) => { - const newValue = observable.read(reader); - const lastValue = _lastValue; - _lastValue = newValue; - handler({ lastValue, newValue }); - }, name); -} - - -// == Lazy Derived == - -export function derivedObservable(name: string, computeFn: (reader: IReader) => T): IObservable { - return new LazyDerived(computeFn, name); -} -export class LazyDerived extends ConvenientObservable { - private readonly observer: LazyDerivedObserver; - - constructor(computeFn: (reader: IReader) => T, name: string) { - super(); - this.observer = new LazyDerivedObserver(computeFn, name, this); - } - - public subscribe(observer: IObserver): void { - this.observer.subscribe(observer); - } - - public unsubscribe(observer: IObserver): void { - this.observer.unsubscribe(observer); - } - - public override read(reader: IReader): T { - return this.observer.read(reader); - } - - public get(): T { - return this.observer.get(); - } -} - -/** - * @internal - */ -class LazyDerivedObserver - extends BaseObservable - implements IReader, IObserver { - private hadValue = false; - private hasValue = false; - private value: T | undefined = undefined; - private updateCount = 0; - - private _dependencies = new Set>(); - public get dependencies(): ReadonlySet> { - return this._dependencies; - } - - /** - * Dependencies that have to be removed when {@link runFn} ran through. - */ - private staleDependencies = new Set>(); - - constructor( - private readonly computeFn: (reader: IReader) => T, - public readonly name: string, - private readonly actualObservable: LazyDerived, - ) { - super(); - } - - protected override onLastObserverUnsubscribed(): void { - /** - * We are not tracking changes anymore, thus we have to assume - * that our cache is invalid. - */ - this.hasValue = false; - this.hadValue = false; - this.value = undefined; - for (const d of this._dependencies) { - d.unsubscribe(this); - } - this._dependencies.clear(); - } - - public handleBeforeReadObservable(observable: IObservable) { - this._dependencies.add(observable); - if (!this.staleDependencies.delete(observable)) { - observable.subscribe(this); - } - } - - public handleChange() { - if (this.hasValue) { - this.hadValue = true; - this.hasValue = false; - } - - // Not in transaction: Recompute & inform observers immediately - if (this.updateCount === 0 && this.observers.size > 0) { - this.get(); - } - - // Otherwise, recompute in `endUpdate` or on demand. - } - - public beginUpdate() { - if (this.updateCount === 0) { - for (const r of this.observers) { - r.beginUpdate(this); - } - } - this.updateCount++; - } - - public endUpdate() { - this.updateCount--; - if (this.updateCount === 0) { - if (this.observers.size > 0) { - // Propagate invalidation - this.get(); - } - - for (const r of this.observers) { - r.endUpdate(this); - } - } - } - - public get(): T { - if (this.observers.size === 0) { - // Cache is not valid and don't refresh the cache. - // Observables should not be read in non-reactive contexts. - return this.computeFn(this); - } - - if (this.updateCount > 0 && this.hasValue) { - // Refresh dependencies - for (const d of this._dependencies) { - // Maybe `.get()` triggers `handleChange`? - d.get(); - if (!this.hasValue) { - // The other dependencies will refresh on demand - break; - } - } - } - - if (!this.hasValue) { - const emptySet = this.staleDependencies; - this.staleDependencies = this._dependencies; - this._dependencies = emptySet; - - const oldValue = this.value; - try { - this.value = this.computeFn(this); - } finally { - // We don't want our observed observables to think that they are (not even temporarily) not being observed. - // Thus, we only unsubscribe from observables that are definitely not read anymore. - for (const o of this.staleDependencies) { - o.unsubscribe(this); - } - this.staleDependencies.clear(); - } - - this.hasValue = true; - if (this.hadValue && oldValue !== this.value) { - for (const r of this.observers) { - r.handleChange(this.actualObservable, undefined); - } - } - } - return this.value!; - } -} - -export namespace LazyDerived { - export const Observer = LazyDerivedObserver; -} - -export function observableFromPromise(promise: Promise): IObservable<{ value?: T }> { - const observable = new ObservableValue<{ value?: T }>({}, 'promiseValue'); - promise.then((value) => { - observable.set({ value }, undefined); - }); - return observable; -} - -export function waitForState(observable: IObservable, predicate: (state: T) => state is TState): Promise; -export function waitForState(observable: IObservable, predicate: (state: T) => boolean): Promise; -export function waitForState(observable: IObservable, predicate: (state: T) => boolean): Promise { - return new Promise(resolve => { - const d = autorun(reader => { - const currentState = observable.read(reader); - if (predicate(currentState)) { - d.dispose(); - resolve(currentState); - } - }, 'waitForState'); - }); -} - -export function observableFromEvent( - event: Event, - getValue: (args: TArgs | undefined) => T -): IObservable { - return new FromEventObservable(event, getValue); -} - -class FromEventObservable extends BaseObservable { - private value: T | undefined; - private hasValue = false; - private subscription: IDisposable | undefined; - - constructor( - private readonly event: Event, - private readonly getValue: (args: TArgs | undefined) => T - ) { - super(); - } - - protected override onFirstObserverSubscribed(): void { - this.subscription = this.event(this.handleEvent); - } - - private readonly handleEvent = (args: TArgs | undefined) => { - const newValue = this.getValue(args); - if (this.value !== newValue) { - this.value = newValue; - - if (this.hasValue) { - transaction(tx => { - for (const o of this.observers) { - tx.updateObserver(o, this); - o.handleChange(this, undefined); - } - }); - } - this.hasValue = true; - } - }; - - protected override onLastObserverUnsubscribed(): void { - this.subscription!.dispose(); - this.subscription = undefined; - this.hasValue = false; - this.value = undefined; - } - - public get(): T { - if (this.subscription) { - if (!this.hasValue) { - this.handleEvent(undefined); - } - return this.value!; - } else { - // no cache, as there are no subscribers to clean it up - return this.getValue(undefined); - } - } -} - -export namespace observableFromEvent { - export const Observer = FromEventObservable; -} - -export function debouncedObservable(observable: IObservable, debounceMs: number, disposableStore: DisposableStore): IObservable { - const debouncedObservable = new ObservableValue(undefined, 'debounced'); - - let timeout: any = undefined; - - disposableStore.add(autorun(reader => { - const value = observable.read(reader); - - if (timeout) { - clearTimeout(timeout); - } - timeout = setTimeout(() => { - transaction(tx => { - debouncedObservable.set(value, tx); - }); - }, debounceMs); - - }, 'debounce')); - - return debouncedObservable; -} - -export function wasEventTriggeredRecently(event: Event, timeoutMs: number, disposableStore: DisposableStore): IObservable { - const observable = new ObservableValue(false, 'triggeredRecently'); - - let timeout: any = undefined; - - disposableStore.add(event(() => { - observable.set(true, undefined); - - if (timeout) { - clearTimeout(timeout); - } - timeout = setTimeout(() => { - observable.set(false, undefined); - }, timeoutMs); - })); - - return observable; -} - -/** - * This ensures the observable is kept up-to-date. - * This is useful when the observables `get` method is used. -*/ -export function keepAlive(observable: IObservable): IDisposable { - return autorun(reader => { - observable.read(reader); - }, 'keep-alive'); -} - -export function derivedObservableWithCache(name: string, computeFn: (reader: IReader, lastValue: T | undefined) => T): IObservable { - let lastValue: T | undefined = undefined; - const observable = derivedObservable(name, reader => { - lastValue = computeFn(reader, lastValue); - return lastValue; - }); - return observable; -} - -export function derivedObservableWithWritableCache(name: string, computeFn: (reader: IReader, lastValue: T | undefined) => T): IObservable & { clearCache(transaction: ITransaction): void } { - let lastValue: T | undefined = undefined; - const counter = new ObservableValue(0, 'counter'); - const observable = derivedObservable(name, reader => { - counter.read(reader); - lastValue = computeFn(reader, lastValue); - return lastValue; - }); - return Object.assign(observable, { - clearCache: (transaction: ITransaction) => { - lastValue = undefined; - counter.set(counter.get() + 1, transaction); - }, - }); +export function derivedObservable(name: string, computeFn: (reader: observable.IReader) => T): observable.IObservable { + return observable.derived(name, computeFn); } diff --git a/src/vs/workbench/contrib/audioCues/test/browser/observable.test.ts b/src/vs/workbench/contrib/audioCues/test/browser/observable.test.ts new file mode 100644 index 00000000000..646464cb636 --- /dev/null +++ b/src/vs/workbench/contrib/audioCues/test/browser/observable.test.ts @@ -0,0 +1,468 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { Emitter } from 'vs/base/common/event'; +import { autorun, autorun2, BaseObservable, derivedObservable, IObserver, ITransaction, observableFromEvent, observableValue, ObservableValue, transaction } from 'vs/workbench/contrib/audioCues/browser/observable'; + +suite('observable integration', () => { + test('basic observable + autorun', () => { + const log = new Log(); + const observable = new ObservableValue(0, 'MyObservableValue'); + + autorun((reader) => { + log.log(`value: ${observable.read(reader)}`); + }, 'MyAutorun'); + assert.deepStrictEqual(log.getAndClearEntries(), ['value: 0']); + + observable.set(1, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), ['value: 1']); + + observable.set(1, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + transaction((tx) => { + observable.set(2, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + observable.set(3, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + }); + + assert.deepStrictEqual(log.getAndClearEntries(), ['value: 3']); + }); + + test('basic computed + autorun', () => { + const log = new Log(); + const observable1 = new ObservableValue(0, 'MyObservableValue1'); + const observable2 = new ObservableValue(0, 'MyObservableValue2'); + + const computed = derivedObservable('computed', (reader) => { + const value1 = observable1.read(reader); + const value2 = observable2.read(reader); + const sum = value1 + value2; + log.log(`recompute: ${value1} + ${value2} = ${sum}`); + return sum; + }); + + autorun((reader) => { + log.log(`value: ${computed.read(reader)}`); + }, 'MyAutorun'); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute: 0 + 0 = 0', + 'value: 0', + ]); + + observable1.set(1, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute: 1 + 0 = 1', + 'value: 1', + ]); + + observable2.set(1, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute: 1 + 1 = 2', + 'value: 2', + ]); + + transaction((tx) => { + observable1.set(5, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + observable2.set(5, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + }); + + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute: 5 + 5 = 10', + 'value: 10', + ]); + + transaction((tx) => { + observable1.set(6, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + observable2.set(4, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + }); + + assert.deepStrictEqual(log.getAndClearEntries(), ['recompute: 6 + 4 = 10']); + }); + + test('read during transaction', () => { + const log = new Log(); + const observable1 = new ObservableValue(0, 'MyObservableValue1'); + const observable2 = new ObservableValue(0, 'MyObservableValue2'); + + const computed = derivedObservable('computed', (reader) => { + const value1 = observable1.read(reader); + const value2 = observable2.read(reader); + const sum = value1 + value2; + log.log(`recompute: ${value1} + ${value2} = ${sum}`); + return sum; + }); + + autorun((reader) => { + log.log(`value: ${computed.read(reader)}`); + }, 'MyAutorun'); + + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute: 0 + 0 = 0', + 'value: 0', + ]); + + log.log(`computed is ${computed.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), ['computed is 0']); + + transaction((tx) => { + observable1.set(-1, tx); + log.log(`computed is ${computed.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute: -1 + 0 = -1', + 'computed is -1', + ]); + + log.log(`computed is ${computed.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), ['computed is -1']); + + observable2.set(1, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + }); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute: -1 + 1 = 0', + 'value: 0', + ]); + }); + + test('topological order', () => { + const log = new Log(); + const observable1 = new ObservableValue(0, 'MyObservableValue1'); + const observable2 = new ObservableValue(0, 'MyObservableValue2'); + + const computed1 = derivedObservable('computed1', (reader) => { + const value1 = observable1.read(reader); + const value2 = observable2.read(reader); + const sum = value1 + value2; + log.log(`recompute1: ${value1} + ${value2} = ${sum}`); + return sum; + }); + + const computed2 = derivedObservable('computed2', (reader) => { + const value1 = computed1.read(reader); + const value2 = observable1.read(reader); + const value3 = observable2.read(reader); + const sum = value1 + value2 + value3; + log.log(`recompute2: ${value1} + ${value2} + ${value3} = ${sum}`); + return sum; + }); + + const computed3 = derivedObservable('computed3', (reader) => { + const value1 = computed2.read(reader); + const value2 = observable1.read(reader); + const value3 = observable2.read(reader); + const sum = value1 + value2 + value3; + log.log(`recompute3: ${value1} + ${value2} + ${value3} = ${sum}`); + return sum; + }); + + autorun((reader) => { + log.log(`value: ${computed3.read(reader)}`); + }, 'MyAutorun'); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute1: 0 + 0 = 0', + 'recompute2: 0 + 0 + 0 = 0', + 'recompute3: 0 + 0 + 0 = 0', + 'value: 0', + ]); + + observable1.set(1, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute1: 1 + 0 = 1', + 'recompute2: 1 + 1 + 0 = 2', + 'recompute3: 2 + 1 + 0 = 3', + 'value: 3', + ]); + + transaction((tx) => { + observable1.set(2, tx); + log.log(`computed2: ${computed2.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute1: 2 + 0 = 2', + 'recompute2: 2 + 2 + 0 = 4', + 'computed2: 4', + ]); + + observable1.set(3, tx); + log.log(`computed2: ${computed2.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute1: 3 + 0 = 3', + 'recompute2: 3 + 3 + 0 = 6', + 'computed2: 6', + ]); + }); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute3: 6 + 3 + 0 = 9', + 'value: 9', + ]); + }); + + test('transaction from autorun', () => { + const log = new Log(); + + const observable1 = new ObservableValue(0, 'MyObservableValue1'); + const observable2 = new ObservableValue(0, 'MyObservableValue2'); + + const computed = derivedObservable('computed', (reader) => { + const value1 = observable1.read(reader); + const value2 = observable2.read(reader); + const sum = value1 + value2; + log.log(`recompute: ${value1} + ${value2} = ${sum}`); + return sum; + }); + + autorun((reader) => { + log.log(`value: ${computed.read(reader)}`); + transaction(tx => { + + }); + + }, 'autorun'); + + + }); + + test('from event', () => { + const log = new Log(); + + let value = 0; + const eventEmitter = new Emitter(); + + let id = 0; + const observable = observableFromEvent( + (handler) => { + const curId = id++; + log.log(`subscribed handler ${curId}`); + const disposable = eventEmitter.event(handler); + + return { + dispose: () => { + log.log(`unsubscribed handler ${curId}`); + disposable.dispose(); + }, + }; + }, + () => { + log.log(`compute value ${value}`); + return value; + } + ); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + log.log(`get value: ${observable.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'compute value 0', + 'get value: 0', + ]); + + log.log(`get value: ${observable.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'compute value 0', + 'get value: 0', + ]); + + const shouldReadObservable = new ObservableValue( + true, + 'shouldReadObservable' + ); + + const autorunDisposable = autorun((reader) => { + if (shouldReadObservable.read(reader)) { + observable.read(reader); + log.log( + `autorun, should read: true, value: ${observable.read(reader)}` + ); + } else { + log.log(`autorun, should read: false`); + } + }, 'MyAutorun'); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'subscribed handler 0', + 'compute value 0', + 'autorun, should read: true, value: 0', + ]); + + log.log(`get value: ${observable.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), ['get value: 0']); + + value = 1; + eventEmitter.fire(); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'compute value 1', + 'autorun, should read: true, value: 1', + ]); + + shouldReadObservable.set(false, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'autorun, should read: false', + 'unsubscribed handler 0', + ]); + + shouldReadObservable.set(true, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'subscribed handler 1', + 'compute value 1', + 'autorun, should read: true, value: 1', + ]); + + autorunDisposable.dispose(); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'unsubscribed handler 1', + ]); + }); + + test('get without observers', () => { + // Maybe this scenario should not be supported. + + const log = new Log(); + const observable1 = new ObservableValue(0, 'MyObservableValue1'); + const computed1 = derivedObservable('computed', (reader) => { + const value1 = observable1.read(reader); + const result = value1 % 3; + log.log(`recompute1: ${value1} % 3 = ${result}`); + return result; + }); + const computed2 = derivedObservable('computed', (reader) => { + const value1 = computed1.read(reader); + + const result = value1 * 2; + log.log(`recompute2: ${value1} * 2 = ${result}`); + return result; + }); + const computed3 = derivedObservable('computed', (reader) => { + const value1 = computed1.read(reader); + + const result = value1 * 3; + log.log(`recompute3: ${value1} * 3 = ${result}`); + return result; + }); + const computedSum = derivedObservable('computed', (reader) => { + const value1 = computed2.read(reader); + const value2 = computed3.read(reader); + + const result = value1 + value2; + log.log(`recompute4: ${value1} + ${value2} = ${result}`); + return result; + }); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + observable1.set(1, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + log.log(`value: ${computedSum.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute1: 1 % 3 = 1', + 'recompute2: 1 * 2 = 2', + 'recompute3: 1 * 3 = 3', + 'recompute4: 2 + 3 = 5', + 'value: 5', + ]); + + log.log(`value: ${computedSum.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute1: 1 % 3 = 1', + 'recompute2: 1 * 2 = 2', + 'recompute3: 1 * 3 = 3', + 'recompute4: 2 + 3 = 5', + 'value: 5', + ]); + }); +}); + +suite('observable details', () => { + test('1', () => { + const log = new Log(); + + class TrackedObservableValue extends BaseObservable { + private value: T; + + constructor(initialValue: T) { + super(); + this.value = initialValue; + } + + public override addObserver(observer: IObserver): void { + log.log(`observable.addObserver ${observer.toString()}`); + super.addObserver(observer); + } + + public override removeObserver(observer: IObserver): void { + log.log(`observable.removeObserver ${observer.toString()}`); + super.removeObserver(observer); + } + + public get(): T { + log.log('observable.get'); + return this.value; + } + + public set(value: T, tx: ITransaction): void { + log.log(`observable.set (value ${value})`); + + if (this.value === value) { + return; + } + this.value = value; + for (const observer of this.observers) { + tx.updateObserver(observer, this); + observer.handleChange(this, undefined); + } + } + } + + const shouldReadObservable = observableValue('shouldReadObservable', true); + const observable = new TrackedObservableValue(0); + const computed = derivedObservable('test', reader => { + if (shouldReadObservable.read(reader)) { + return observable.read(reader) * 2; + } + return 1; + }); + autorun2('test', reader => { + const value = computed.read(reader); + log.log(`autorun: ${value}`); + }); + + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'observable.addObserver LazyDerived', + 'observable.get', + 'autorun: 0', + ]); + + transaction(tx => { + observable.set(1, tx); + assert.deepStrictEqual(log.getAndClearEntries(), (["observable.set (value 1)"])); + + shouldReadObservable.set(false, tx); + assert.deepStrictEqual(log.getAndClearEntries(), ([])); + + computed.get(); + assert.deepStrictEqual(log.getAndClearEntries(), (["observable.removeObserver LazyDerived"])); + }); + assert.deepStrictEqual(log.getAndClearEntries(), (["autorun: 1"])); + }); +}); + +class Log { + private readonly entries: string[] = []; + public log(message: string): void { + this.entries.push(message); + } + + public getAndClearEntries(): string[] { + const entries = [...this.entries]; + this.entries.length = 0; + return entries; + } +} From 2a447e5e3aef839b96b6301329bd67085232ff1f Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Sun, 3 Jul 2022 19:46:52 +0200 Subject: [PATCH 132/150] Improves 3wm observables. --- .../browser/model/mergeEditorModel.ts | 14 +- .../browser/model/modifiedBaseRange.ts | 4 + .../browser/model/textModelDiffs.ts | 2 + .../contrib/mergeEditor/browser/utils.ts | 2 +- .../mergeEditor/browser/view/editorGutter.ts | 29 +- .../browser/view/editors/codeEditorView.ts | 15 +- .../view/editors/inputCodeEditorView.ts | 267 +++++++++--------- .../view/editors/resultCodeEditorView.ts | 4 +- .../mergeEditor/browser/view/viewModel.ts | 1 + 9 files changed, 183 insertions(+), 155 deletions(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts index d9f61d89baf..2c1f3b1c4e7 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts @@ -44,7 +44,7 @@ export class MergeEditorModel extends EditorModel { return MergeEditorModelState.upToDate; }); - public readonly isUpToDate = derivedObservable('isUpdating', reader => this.state.read(reader) === MergeEditorModelState.upToDate); + public readonly isUpToDate = derivedObservable('isUpToDate', reader => this.state.read(reader) === MergeEditorModelState.upToDate); public readonly onInitialized = waitForState(this.state, state => state === MergeEditorModelState.upToDate); @@ -84,7 +84,7 @@ export class MergeEditorModel extends EditorModel { return map.size - handledCount; }); - public readonly hasUnhandledConflicts = this.unhandledConflictsCount.map(value => value > 0); + public readonly hasUnhandledConflicts = this.unhandledConflictsCount.map(value => /** @description hasUnhandledConflicts */ value > 0); public readonly input1ResultMapping = derivedObservable('input1ResultMapping', reader => { const resultDiffs = this.resultDiffs.read(reader); @@ -145,7 +145,7 @@ export class MergeEditorModel extends EditorModel { let shouldResetHandlingState = true; this._register( autorunHandleChanges( - 'Recompute State', + 'Merge Editor Model: Recompute State', { handleChange: (ctx) => { if (ctx.didChange(this.modifiedBaseRangeHandlingStateStores)) { @@ -165,6 +165,7 @@ export class MergeEditorModel extends EditorModel { const resultDiffs = this.resultTextModelDiffs.diffs.read(reader); const stores = this.modifiedBaseRangeStateStores.read(reader); transaction(tx => { + /** @description Merge Editor Model: Recompute State */ this.recomputeState(resultDiffs, stores, tx); if (shouldResetHandlingState) { shouldResetHandlingState = false; @@ -202,12 +203,16 @@ export class MergeEditorModel extends EditorModel { ); for (const row of baseRangeWithStoreAndTouchingDiffs) { - row.left[1].set(this.computeState(row.left[0], row.rights), tx); + const newState = this.computeState(row.left[0], row.rights); + if (!row.left[1].get().equals(newState)) { + row.left[1].set(newState, tx); + } } } public resetUnknown(): void { transaction(tx => { + /** @description Reset Unknown Base Range States */ for (const range of this.modifiedBaseRanges.get()) { if (this.getState(range).get().conflicting) { this.setState(range, ModifiedBaseRangeState.default, false, tx); @@ -218,6 +223,7 @@ export class MergeEditorModel extends EditorModel { public mergeNonConflictingDiffs(): void { transaction((tx) => { + /** @description Merge None Conflicting Diffs */ for (const m of this.modifiedBaseRanges.get()) { if (m.isConflicting) { continue; diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange.ts index 4dadbb6816a..028b9afe986 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange.ts @@ -293,6 +293,10 @@ export class ModifiedBaseRangeState { } return arr.join(','); } + + equals(newState: ModifiedBaseRangeState): boolean { + return this.input1 === newState.input1 && this.input2 === newState.input2 && this.input2First === newState.input2First; + } } export const enum InputState { diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts index c57c0ff51ea..ed894cc7e25 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts @@ -54,6 +54,7 @@ export class TextModelDiffs extends Disposable { } transaction(tx => { + /** @description Starting Diff Computation. */ this._state.set( initializing ? TextModelDiffState.initializing : TextModelDiffState.updating, tx, @@ -72,6 +73,7 @@ export class TextModelDiffs extends Disposable { } transaction(tx => { + /** @description Completed Diff Computation */ if (result.diffs) { this._state.set(TextModelDiffState.upToDate, tx, TextModelDiffChangeReason.textChange); this._diffs.set(result.diffs, tx, TextModelDiffChangeReason.textChange); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/utils.ts b/src/vs/workbench/contrib/mergeEditor/browser/utils.ts index 23277aeae95..3188a3b2e7a 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/utils.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/utils.ts @@ -79,7 +79,7 @@ export function applyObservableDecorations(editor: CodeEditorWidget, decorations editor.changeDecorations(a => { decorationIds = a.deltaDecorations(decorationIds, d); }); - }, 'Update Decorations')); + }, `Apply decorations from ${decorations.debugName}`)); d.add({ dispose: () => { editor.changeDecorations(a => { diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts index 08a13900476..0a54f686ca4 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts @@ -5,21 +5,24 @@ import { h } from 'vs/base/browser/dom'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { observableSignalFromEvent } from 'vs/base/common/observable'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; -import { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable'; +import { autorun, IReader, observableFromEvent } from 'vs/workbench/contrib/audioCues/browser/observable'; import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange'; export class EditorGutter extends Disposable { private readonly scrollTop = observableFromEvent( this._editor.onDidScrollChange, - (e) => this._editor.getScrollTop() + (e) => /** @description editor.onDidScrollChange */ this._editor.getScrollTop() ); + private readonly isScrollTopZero = this.scrollTop.map((scrollTop) => /** @description isScrollTopZero */ scrollTop === 0); private readonly modelAttached = observableFromEvent( this._editor.onDidChangeModel, - (e) => this._editor.hasModel() + (e) => /** @description editor.onDidChangeModel */ this._editor.hasModel() ); - private readonly changeCounter = new ObservableValue(0, 'counter'); + private readonly editorOnDidChangeViewZones = observableSignalFromEvent('onDidChangeViewZones', this._editor.onDidChangeViewZones); + private readonly editorOnDidContentSizeChange = observableSignalFromEvent('onDidContentSizeChange', this._editor.onDidContentSizeChange); constructor( private readonly _editor: CodeEditorWidget, @@ -34,19 +37,10 @@ export class EditorGutter extends D ); this._register(autorun((reader) => { - scrollDecoration.className = this.scrollTop.read(reader) === 0 ? '' : 'scroll-decoration'; + scrollDecoration.className = this.isScrollTopZero.read(reader) ? '' : 'scroll-decoration'; }, 'update scroll decoration')); - - this._register(autorun((reader) => this.render(reader), 'Render')); - - this._editor.onDidChangeViewZones(e => { - this.changeCounter.set(this.changeCounter.get() + 1, undefined); - }); - - this._editor.onDidContentSizeChange(e => { - this.changeCounter.set(this.changeCounter.get() + 1, undefined); - }); + this._register(autorun((reader) => this.render(reader), 'EditorGutter.Render')); } private readonly views = new Map(); @@ -55,7 +49,10 @@ export class EditorGutter extends D if (!this.modelAttached.read(reader)) { return; } - this.changeCounter.read(reader); + + this.editorOnDidChangeViewZones.read(reader); + this.editorOnDidContentSizeChange.read(reader); + const scrollTop = this.scrollTop.read(reader); const visibleRanges = this._editor.getVisibleRanges(); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts index e9a1b888ce0..415eea716ce 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts @@ -14,14 +14,14 @@ import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { ITextModel } from 'vs/editor/common/model'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { DEFAULT_EDITOR_MAX_DIMENSIONS, DEFAULT_EDITOR_MIN_DIMENSIONS } from 'vs/workbench/browser/parts/editor/editor'; -import { IObservable, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable'; +import { IObservable, observableFromEvent, ObservableValue, transaction } from 'vs/workbench/contrib/audioCues/browser/observable'; import { setStyle } from 'vs/workbench/contrib/mergeEditor/browser/utils'; import { MergeEditorViewModel } from 'vs/workbench/contrib/mergeEditor/browser/view/viewModel'; export abstract class CodeEditorView extends Disposable { private readonly _viewModel = new ObservableValue(undefined, 'viewModel'); readonly viewModel: IObservable = this._viewModel; - readonly model = this._viewModel.map(m => m?.model); + readonly model = this._viewModel.map(m => /** @description model */ m?.model); protected readonly htmlElements = h('div.code-view', [ h('div.title', { $: 'title' }), @@ -71,15 +71,15 @@ export abstract class CodeEditorView extends Disposable { public readonly isFocused = observableFromEvent( Event.any(this.editor.onDidBlurEditorWidget, this.editor.onDidFocusEditorWidget), - () => this.editor.hasWidgetFocus() + () => /** @description editor.hasWidgetFocus */ this.editor.hasWidgetFocus() ); public readonly cursorPosition = observableFromEvent( this.editor.onDidChangeCursorPosition, - () => this.editor.getPosition() + () => /** @description editor.getPosition */ this.editor.getPosition() ); - public readonly cursorLineNumber = this.cursorPosition.map(p => p?.lineNumber); + public readonly cursorLineNumber = this.cursorPosition.map(p => /** @description cursorPosition.lineNumber */ p?.lineNumber); constructor( @IInstantiationService @@ -103,6 +103,9 @@ export abstract class CodeEditorView extends Disposable { this._title.setLabel(title, description); this._detail.setLabel('', detail); - this._viewModel.set(viewModel, undefined); + transaction(tx => { + /** @description CodeEditorView: Set Model */ + this._viewModel.set(viewModel, tx); + }); } } diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts index aadd274e6e1..476e5143570 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts @@ -8,6 +8,7 @@ import { Toggle } from 'vs/base/browser/ui/toggle/toggle'; import { Action, IAction, Separator } from 'vs/base/common/actions'; import { Codicon } from 'vs/base/common/codicons'; import { Disposable } from 'vs/base/common/lifecycle'; +import { derived, ISettableObservable } from 'vs/base/common/observable'; import { noBreakWhitespace } from 'vs/base/common/strings'; import { isDefined } from 'vs/base/common/types'; import { EditorExtensionsRegistry, IEditorContributionDescription } from 'vs/editor/browser/editorExtensions'; @@ -26,7 +27,7 @@ import { EditorGutter, IGutterItemInfo, IGutterItemView } from '../editorGutter' import { CodeEditorView } from './codeEditorView'; export class InputCodeEditorView extends CodeEditorView { - private readonly decorations = derivedObservable('decorations', reader => { + private readonly decorations = derivedObservable(`input${this.inputNumber}.decorations`, reader => { const viewModel = this.viewModel.read(reader); if (!viewModel) { return []; @@ -99,6 +100,136 @@ export class InputCodeEditorView extends CodeEditorView { return result; }); + private readonly modifiedBaseRangeGutterItemInfos = derived(`input${this.inputNumber}.modifiedBaseRangeGutterItemInfos`, reader => { + const viewModel = this.viewModel.read(reader); + if (!viewModel) { return []; } + const model = viewModel.model; + const inputNumber = this.inputNumber; + + return model.modifiedBaseRanges.read(reader) + .filter((r) => r.getInputDiffs(this.inputNumber).length > 0) + .map((baseRange, idx) => ({ + id: idx.toString(), + range: baseRange.getInputRange(this.inputNumber), + enabled: model.isUpToDate, + toggleState: derivedObservable('checkbox is checked', (reader) => { + const input = model + .getState(baseRange) + .read(reader) + .getInput(this.inputNumber); + return input === InputState.second && !baseRange.isOrderRelevant + ? InputState.first + : input; + } + ), + setState: (value, tx) => viewModel.setState( + baseRange, + model + .getState(baseRange) + .get() + .withInputValue(this.inputNumber, value), + tx + ), + toggleBothSides() { + transaction(tx => { + /** @description Context Menu: toggle both sides */ + const state = model + .getState(baseRange) + .get(); + model.setState( + baseRange, + state + .toggle(inputNumber) + .toggle(inputNumber === 1 ? 2 : 1), + true, + tx + ); + }); + }, + getContextMenuActions: () => { + const state = model.getState(baseRange).get(); + const handled = model.isHandled(baseRange).get(); + + const update = (newState: ModifiedBaseRangeState) => { + transaction(tx => { + /** @description Context Menu: Update Base Range State */ + return viewModel.setState(baseRange, newState, tx); + }); + }; + + function action(id: string, label: string, targetState: ModifiedBaseRangeState, checked: boolean) { + const action = new Action(id, label, undefined, true, () => { + update(targetState); + }); + action.checked = checked; + return action; + } + const both = state.input1 && state.input2; + + return [ + baseRange.input1Diffs.length > 0 + ? action( + 'mergeEditor.acceptInput1', + localize('mergeEditor.accept', 'Accept {0}', model.input1Title), + state.toggle(1), + state.input1 + ) + : undefined, + baseRange.input2Diffs.length > 0 + ? action( + 'mergeEditor.acceptInput2', + localize('mergeEditor.accept', 'Accept {0}', model.input2Title), + state.toggle(2), + state.input2 + ) + : undefined, + baseRange.isConflicting + ? setFields( + action( + 'mergeEditor.acceptBoth', + localize( + 'mergeEditor.acceptBoth', + 'Accept Both' + ), + state.withInput1(!both).withInput2(!both), + both + ), + { enabled: baseRange.canBeCombined } + ) + : undefined, + new Separator(), + baseRange.isConflicting + ? setFields( + action( + 'mergeEditor.swap', + localize('mergeEditor.swap', 'Swap'), + state.swap(), + false + ), + { enabled: !state.isEmpty && (!both || baseRange.isOrderRelevant) } + ) + : undefined, + + setFields( + new Action( + 'mergeEditor.markAsHandled', + localize('mergeEditor.markAsHandled', 'Mark as Handled'), + undefined, + true, + () => { + transaction((tx) => { + /** @description Context Menu: Mark as handled */ + model.setHandled(baseRange, !handled, tx); + }); + } + ), + { checked: handled } + ), + ].filter(isDefined); + } + })); + }); + constructor( public readonly inputNumber: 1 | 2, @IInstantiationService instantiationService: IInstantiationService, @@ -112,127 +243,7 @@ export class InputCodeEditorView extends CodeEditorView { this._register( new EditorGutter(this.editor, this.htmlElements.gutterDiv, { getIntersectingGutterItems: (range, reader) => { - const viewModel = this.viewModel.read(reader); - if (!viewModel) { return []; } - const model = viewModel.model; - - return model.modifiedBaseRanges.read(reader) - .filter((r) => r.getInputDiffs(this.inputNumber).length > 0) - .map((baseRange, idx) => ({ - id: idx.toString(), - range: baseRange.getInputRange(this.inputNumber), - enabled: model.isUpToDate, - toggleState: derivedObservable('toggle', (reader) => { - const input = model - .getState(baseRange) - .read(reader) - .getInput(this.inputNumber); - return input === InputState.second && !baseRange.isOrderRelevant - ? InputState.first - : input; - } - ), - setState: (value, tx) => viewModel.setState( - baseRange, - model - .getState(baseRange) - .get() - .withInputValue(this.inputNumber, value), - tx - ), - toggleBothSides() { - transaction(tx => { - const state = model - .getState(baseRange) - .get(); - model.setState( - baseRange, - state - .toggle(inputNumber) - .toggle(inputNumber === 1 ? 2 : 1), - true, - tx - ); - }); - }, - getContextMenuActions: () => { - const state = model.getState(baseRange).get(); - const handled = model.isHandled(baseRange).get(); - - const update = (newState: ModifiedBaseRangeState) => { - transaction(tx => viewModel.setState(baseRange, newState, tx)); - }; - - function action(id: string, label: string, targetState: ModifiedBaseRangeState, checked: boolean) { - const action = new Action(id, label, undefined, true, () => { - update(targetState); - }); - action.checked = checked; - return action; - } - const both = state.input1 && state.input2; - - return [ - baseRange.input1Diffs.length > 0 - ? action( - 'mergeEditor.acceptInput1', - localize('mergeEditor.accept', 'Accept {0}', model.input1Title), - state.toggle(1), - state.input1 - ) - : undefined, - baseRange.input2Diffs.length > 0 - ? action( - 'mergeEditor.acceptInput2', - localize('mergeEditor.accept', 'Accept {0}', model.input2Title), - state.toggle(2), - state.input2 - ) - : undefined, - baseRange.isConflicting - ? setFields( - action( - 'mergeEditor.acceptBoth', - localize( - 'mergeEditor.acceptBoth', - 'Accept Both' - ), - state.withInput1(!both).withInput2(!both), - both - ), - { enabled: baseRange.canBeCombined } - ) - : undefined, - new Separator(), - baseRange.isConflicting - ? setFields( - action( - 'mergeEditor.swap', - localize('mergeEditor.swap', 'Swap'), - state.swap(), - false - ), - { enabled: !state.isEmpty && (!both || baseRange.isOrderRelevant) } - ) - : undefined, - - setFields( - new Action( - 'mergeEditor.markAsHandled', - localize('mergeEditor.markAsHandled', 'Mark as Handled'), - undefined, - true, - () => { - transaction((tx) => { - model.setHandled(baseRange, !handled, tx); - }); - } - ), - { checked: handled } - ), - ].filter(isDefined); - } - })); + return this.modifiedBaseRangeGutterItemInfos.read(reader); }, createView: (item, target) => new MergeConflictGutterItemView(item, target, contextMenuService, themeService), }) @@ -253,7 +264,7 @@ export interface ModifiedBaseRangeGutterItemInfo extends IGutterItemInfo { } export class MergeConflictGutterItemView extends Disposable implements IGutterItemView { - private readonly item = new ObservableValue(undefined, 'item'); + private readonly item: ISettableObservable; constructor( item: ModifiedBaseRangeGutterItemInfo, @@ -263,7 +274,7 @@ export class MergeConflictGutterItemView extends Disposable implements IGutterIt ) { super(); - this.item.set(item, undefined); + this.item = new ObservableValue(item, 'item'); target.classList.add('merge-accept-gutter-marker'); @@ -315,11 +326,12 @@ export class MergeConflictGutterItemView extends Disposable implements IGutterIt } else { checkBox.enable(); } - }, 'Update Toggle State') + }, 'Update Checkbox') ); this._register(checkBox.onChange(() => { transaction(tx => { + /** @description Handle Checkbox Change */ this.item.get()!.setState(checkBox.checked, tx); }); })); @@ -337,6 +349,9 @@ export class MergeConflictGutterItemView extends Disposable implements IGutterIt } update(baseRange: ModifiedBaseRangeGutterItemInfo): void { - this.item.set(baseRange, undefined); + transaction(tx => { + /** @description MergeConflictGutterItemView: Updating new base range */ + this.item.set(baseRange, tx); + }); } } diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts index fc6919af08a..89e8490cf90 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts @@ -14,7 +14,7 @@ import { handledConflictMinimapOverViewRulerColor, unhandledConflictMinimapOverV import { CodeEditorView } from './codeEditorView'; export class ResultCodeEditorView extends CodeEditorView { - private readonly decorations = derivedObservable('decorations', reader => { + private readonly decorations = derivedObservable('result.decorations', reader => { const viewModel = this.viewModel.read(reader); if (!viewModel) { return []; @@ -125,6 +125,6 @@ export class ResultCodeEditorView extends CodeEditorView { '{0} Remaining Conflicts', count )); - }, 'update label')); + }, 'update remainingConflicts label')); } } diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts index 3f90643ea4a..46c954fb2c3 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts @@ -135,6 +135,7 @@ export class MergeEditorViewModel { return; } transaction(tx => { + /** @description Toggle Active Conflict */ this.setState( activeModifiedBaseRange, this.model.getState(activeModifiedBaseRange).get().toggle(inputNumber), From 3ab72751707134b7a6ec523d6183629f6cfa52ad Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 4 Jul 2022 07:35:17 +0200 Subject: [PATCH 133/150] SCM - Fix issue with commit dropdown button (#154034) Fix issue with commit dropdown button --- src/vs/workbench/contrib/scm/browser/scmViewPane.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 1a733fb7680..47801f55fd9 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -2411,7 +2411,12 @@ export class SCMViewPane extends ViewPane { return; } else if (isSCMActionButton(e.element)) { this.scmViewService.focus(e.element.repository); - this.actionButtonRenderer.focusActionButton(e.element); + + // Focus the action button + const target = e.browserEvent?.target as HTMLElement; + if (target.classList.contains('monaco-tl-row') || target.classList.contains('button-container')) { + this.actionButtonRenderer.focusActionButton(e.element); + } return; } From 2fcd34a41b11b49f0d0f778752a117de292e25e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Mon, 4 Jul 2022 07:41:54 +0200 Subject: [PATCH 134/150] Fixes #148568 (#154008) fixes #148568 --- extensions/git/src/git.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index d6b9ef01b70..18d66247661 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -1785,12 +1785,12 @@ export class Repository { } catch (err) { if (/^error: failed to push some refs to\b/m.test(err.stderr || '')) { err.gitErrorCode = GitErrorCodes.PushRejected; + } else if (/Permission.*denied/.test(err.stderr || '')) { + err.gitErrorCode = GitErrorCodes.PermissionDenied; } else if (/Could not read from remote repository/.test(err.stderr || '')) { err.gitErrorCode = GitErrorCodes.RemoteConnectionError; } else if (/^fatal: The current branch .* has no upstream branch/.test(err.stderr || '')) { err.gitErrorCode = GitErrorCodes.NoUpstreamBranch; - } else if (/Permission.*denied/.test(err.stderr || '')) { - err.gitErrorCode = GitErrorCodes.PermissionDenied; } throw err; From 2471ff08d8d42e30abe2d606ae56c13e639a1d6e Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 4 Jul 2022 10:51:33 +0200 Subject: [PATCH 135/150] Removes old observable test. --- .../audioCues/test/browser/observable.test.ts | 468 ------------------ 1 file changed, 468 deletions(-) delete mode 100644 src/vs/workbench/contrib/audioCues/test/browser/observable.test.ts diff --git a/src/vs/workbench/contrib/audioCues/test/browser/observable.test.ts b/src/vs/workbench/contrib/audioCues/test/browser/observable.test.ts deleted file mode 100644 index 646464cb636..00000000000 --- a/src/vs/workbench/contrib/audioCues/test/browser/observable.test.ts +++ /dev/null @@ -1,468 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import { Emitter } from 'vs/base/common/event'; -import { autorun, autorun2, BaseObservable, derivedObservable, IObserver, ITransaction, observableFromEvent, observableValue, ObservableValue, transaction } from 'vs/workbench/contrib/audioCues/browser/observable'; - -suite('observable integration', () => { - test('basic observable + autorun', () => { - const log = new Log(); - const observable = new ObservableValue(0, 'MyObservableValue'); - - autorun((reader) => { - log.log(`value: ${observable.read(reader)}`); - }, 'MyAutorun'); - assert.deepStrictEqual(log.getAndClearEntries(), ['value: 0']); - - observable.set(1, undefined); - assert.deepStrictEqual(log.getAndClearEntries(), ['value: 1']); - - observable.set(1, undefined); - assert.deepStrictEqual(log.getAndClearEntries(), []); - - transaction((tx) => { - observable.set(2, tx); - assert.deepStrictEqual(log.getAndClearEntries(), []); - - observable.set(3, tx); - assert.deepStrictEqual(log.getAndClearEntries(), []); - }); - - assert.deepStrictEqual(log.getAndClearEntries(), ['value: 3']); - }); - - test('basic computed + autorun', () => { - const log = new Log(); - const observable1 = new ObservableValue(0, 'MyObservableValue1'); - const observable2 = new ObservableValue(0, 'MyObservableValue2'); - - const computed = derivedObservable('computed', (reader) => { - const value1 = observable1.read(reader); - const value2 = observable2.read(reader); - const sum = value1 + value2; - log.log(`recompute: ${value1} + ${value2} = ${sum}`); - return sum; - }); - - autorun((reader) => { - log.log(`value: ${computed.read(reader)}`); - }, 'MyAutorun'); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'recompute: 0 + 0 = 0', - 'value: 0', - ]); - - observable1.set(1, undefined); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'recompute: 1 + 0 = 1', - 'value: 1', - ]); - - observable2.set(1, undefined); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'recompute: 1 + 1 = 2', - 'value: 2', - ]); - - transaction((tx) => { - observable1.set(5, tx); - assert.deepStrictEqual(log.getAndClearEntries(), []); - - observable2.set(5, tx); - assert.deepStrictEqual(log.getAndClearEntries(), []); - }); - - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'recompute: 5 + 5 = 10', - 'value: 10', - ]); - - transaction((tx) => { - observable1.set(6, tx); - assert.deepStrictEqual(log.getAndClearEntries(), []); - - observable2.set(4, tx); - assert.deepStrictEqual(log.getAndClearEntries(), []); - }); - - assert.deepStrictEqual(log.getAndClearEntries(), ['recompute: 6 + 4 = 10']); - }); - - test('read during transaction', () => { - const log = new Log(); - const observable1 = new ObservableValue(0, 'MyObservableValue1'); - const observable2 = new ObservableValue(0, 'MyObservableValue2'); - - const computed = derivedObservable('computed', (reader) => { - const value1 = observable1.read(reader); - const value2 = observable2.read(reader); - const sum = value1 + value2; - log.log(`recompute: ${value1} + ${value2} = ${sum}`); - return sum; - }); - - autorun((reader) => { - log.log(`value: ${computed.read(reader)}`); - }, 'MyAutorun'); - - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'recompute: 0 + 0 = 0', - 'value: 0', - ]); - - log.log(`computed is ${computed.get()}`); - assert.deepStrictEqual(log.getAndClearEntries(), ['computed is 0']); - - transaction((tx) => { - observable1.set(-1, tx); - log.log(`computed is ${computed.get()}`); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'recompute: -1 + 0 = -1', - 'computed is -1', - ]); - - log.log(`computed is ${computed.get()}`); - assert.deepStrictEqual(log.getAndClearEntries(), ['computed is -1']); - - observable2.set(1, tx); - assert.deepStrictEqual(log.getAndClearEntries(), []); - }); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'recompute: -1 + 1 = 0', - 'value: 0', - ]); - }); - - test('topological order', () => { - const log = new Log(); - const observable1 = new ObservableValue(0, 'MyObservableValue1'); - const observable2 = new ObservableValue(0, 'MyObservableValue2'); - - const computed1 = derivedObservable('computed1', (reader) => { - const value1 = observable1.read(reader); - const value2 = observable2.read(reader); - const sum = value1 + value2; - log.log(`recompute1: ${value1} + ${value2} = ${sum}`); - return sum; - }); - - const computed2 = derivedObservable('computed2', (reader) => { - const value1 = computed1.read(reader); - const value2 = observable1.read(reader); - const value3 = observable2.read(reader); - const sum = value1 + value2 + value3; - log.log(`recompute2: ${value1} + ${value2} + ${value3} = ${sum}`); - return sum; - }); - - const computed3 = derivedObservable('computed3', (reader) => { - const value1 = computed2.read(reader); - const value2 = observable1.read(reader); - const value3 = observable2.read(reader); - const sum = value1 + value2 + value3; - log.log(`recompute3: ${value1} + ${value2} + ${value3} = ${sum}`); - return sum; - }); - - autorun((reader) => { - log.log(`value: ${computed3.read(reader)}`); - }, 'MyAutorun'); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'recompute1: 0 + 0 = 0', - 'recompute2: 0 + 0 + 0 = 0', - 'recompute3: 0 + 0 + 0 = 0', - 'value: 0', - ]); - - observable1.set(1, undefined); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'recompute1: 1 + 0 = 1', - 'recompute2: 1 + 1 + 0 = 2', - 'recompute3: 2 + 1 + 0 = 3', - 'value: 3', - ]); - - transaction((tx) => { - observable1.set(2, tx); - log.log(`computed2: ${computed2.get()}`); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'recompute1: 2 + 0 = 2', - 'recompute2: 2 + 2 + 0 = 4', - 'computed2: 4', - ]); - - observable1.set(3, tx); - log.log(`computed2: ${computed2.get()}`); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'recompute1: 3 + 0 = 3', - 'recompute2: 3 + 3 + 0 = 6', - 'computed2: 6', - ]); - }); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'recompute3: 6 + 3 + 0 = 9', - 'value: 9', - ]); - }); - - test('transaction from autorun', () => { - const log = new Log(); - - const observable1 = new ObservableValue(0, 'MyObservableValue1'); - const observable2 = new ObservableValue(0, 'MyObservableValue2'); - - const computed = derivedObservable('computed', (reader) => { - const value1 = observable1.read(reader); - const value2 = observable2.read(reader); - const sum = value1 + value2; - log.log(`recompute: ${value1} + ${value2} = ${sum}`); - return sum; - }); - - autorun((reader) => { - log.log(`value: ${computed.read(reader)}`); - transaction(tx => { - - }); - - }, 'autorun'); - - - }); - - test('from event', () => { - const log = new Log(); - - let value = 0; - const eventEmitter = new Emitter(); - - let id = 0; - const observable = observableFromEvent( - (handler) => { - const curId = id++; - log.log(`subscribed handler ${curId}`); - const disposable = eventEmitter.event(handler); - - return { - dispose: () => { - log.log(`unsubscribed handler ${curId}`); - disposable.dispose(); - }, - }; - }, - () => { - log.log(`compute value ${value}`); - return value; - } - ); - assert.deepStrictEqual(log.getAndClearEntries(), []); - - log.log(`get value: ${observable.get()}`); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'compute value 0', - 'get value: 0', - ]); - - log.log(`get value: ${observable.get()}`); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'compute value 0', - 'get value: 0', - ]); - - const shouldReadObservable = new ObservableValue( - true, - 'shouldReadObservable' - ); - - const autorunDisposable = autorun((reader) => { - if (shouldReadObservable.read(reader)) { - observable.read(reader); - log.log( - `autorun, should read: true, value: ${observable.read(reader)}` - ); - } else { - log.log(`autorun, should read: false`); - } - }, 'MyAutorun'); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'subscribed handler 0', - 'compute value 0', - 'autorun, should read: true, value: 0', - ]); - - log.log(`get value: ${observable.get()}`); - assert.deepStrictEqual(log.getAndClearEntries(), ['get value: 0']); - - value = 1; - eventEmitter.fire(); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'compute value 1', - 'autorun, should read: true, value: 1', - ]); - - shouldReadObservable.set(false, undefined); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'autorun, should read: false', - 'unsubscribed handler 0', - ]); - - shouldReadObservable.set(true, undefined); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'subscribed handler 1', - 'compute value 1', - 'autorun, should read: true, value: 1', - ]); - - autorunDisposable.dispose(); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'unsubscribed handler 1', - ]); - }); - - test('get without observers', () => { - // Maybe this scenario should not be supported. - - const log = new Log(); - const observable1 = new ObservableValue(0, 'MyObservableValue1'); - const computed1 = derivedObservable('computed', (reader) => { - const value1 = observable1.read(reader); - const result = value1 % 3; - log.log(`recompute1: ${value1} % 3 = ${result}`); - return result; - }); - const computed2 = derivedObservable('computed', (reader) => { - const value1 = computed1.read(reader); - - const result = value1 * 2; - log.log(`recompute2: ${value1} * 2 = ${result}`); - return result; - }); - const computed3 = derivedObservable('computed', (reader) => { - const value1 = computed1.read(reader); - - const result = value1 * 3; - log.log(`recompute3: ${value1} * 3 = ${result}`); - return result; - }); - const computedSum = derivedObservable('computed', (reader) => { - const value1 = computed2.read(reader); - const value2 = computed3.read(reader); - - const result = value1 + value2; - log.log(`recompute4: ${value1} + ${value2} = ${result}`); - return result; - }); - assert.deepStrictEqual(log.getAndClearEntries(), []); - - observable1.set(1, undefined); - assert.deepStrictEqual(log.getAndClearEntries(), []); - - log.log(`value: ${computedSum.get()}`); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'recompute1: 1 % 3 = 1', - 'recompute2: 1 * 2 = 2', - 'recompute3: 1 * 3 = 3', - 'recompute4: 2 + 3 = 5', - 'value: 5', - ]); - - log.log(`value: ${computedSum.get()}`); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'recompute1: 1 % 3 = 1', - 'recompute2: 1 * 2 = 2', - 'recompute3: 1 * 3 = 3', - 'recompute4: 2 + 3 = 5', - 'value: 5', - ]); - }); -}); - -suite('observable details', () => { - test('1', () => { - const log = new Log(); - - class TrackedObservableValue extends BaseObservable { - private value: T; - - constructor(initialValue: T) { - super(); - this.value = initialValue; - } - - public override addObserver(observer: IObserver): void { - log.log(`observable.addObserver ${observer.toString()}`); - super.addObserver(observer); - } - - public override removeObserver(observer: IObserver): void { - log.log(`observable.removeObserver ${observer.toString()}`); - super.removeObserver(observer); - } - - public get(): T { - log.log('observable.get'); - return this.value; - } - - public set(value: T, tx: ITransaction): void { - log.log(`observable.set (value ${value})`); - - if (this.value === value) { - return; - } - this.value = value; - for (const observer of this.observers) { - tx.updateObserver(observer, this); - observer.handleChange(this, undefined); - } - } - } - - const shouldReadObservable = observableValue('shouldReadObservable', true); - const observable = new TrackedObservableValue(0); - const computed = derivedObservable('test', reader => { - if (shouldReadObservable.read(reader)) { - return observable.read(reader) * 2; - } - return 1; - }); - autorun2('test', reader => { - const value = computed.read(reader); - log.log(`autorun: ${value}`); - }); - - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'observable.addObserver LazyDerived', - 'observable.get', - 'autorun: 0', - ]); - - transaction(tx => { - observable.set(1, tx); - assert.deepStrictEqual(log.getAndClearEntries(), (["observable.set (value 1)"])); - - shouldReadObservable.set(false, tx); - assert.deepStrictEqual(log.getAndClearEntries(), ([])); - - computed.get(); - assert.deepStrictEqual(log.getAndClearEntries(), (["observable.removeObserver LazyDerived"])); - }); - assert.deepStrictEqual(log.getAndClearEntries(), (["autorun: 1"])); - }); -}); - -class Log { - private readonly entries: string[] = []; - public log(message: string): void { - this.entries.push(message); - } - - public getAndClearEntries(): string[] { - const entries = [...this.entries]; - this.entries.length = 0; - return entries; - } -} From 3559a3c34edd8fb01eb803ec0c63995dbe6d6329 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 4 Jul 2022 11:05:45 +0200 Subject: [PATCH 136/150] Process explorer: indicate extension host better (fix #150820) (#154043) --- src/vs/base/node/ps.ts | 5 +++++ .../extensions/electron-main/extensionHostStarter.ts | 1 + 2 files changed, 6 insertions(+) diff --git a/src/vs/base/node/ps.ts b/src/vs/base/node/ps.ts index 4eded868127..8fd62606254 100644 --- a/src/vs/base/node/ps.ts +++ b/src/vs/base/node/ps.ts @@ -52,6 +52,7 @@ export function listProcesses(rootPid: number): Promise { const ISSUE_REPORTER_HINT = /--vscode-window-kind=issue-reporter/; const PROCESS_EXPLORER_HINT = /--vscode-window-kind=process-explorer/; const UTILITY_NETWORK_HINT = /--utility-sub-type=network/; + const UTILITY_EXTENSION_HOST_HINT = /--vscode-utility-kind=extensionHost/; const WINDOWS_CRASH_REPORTER = /--crashes-directory/; const WINDOWS_PTY = /\\pipe\\winpty-control/; const WINDOWS_CONSOLE_HOST = /conhost\.exe/; @@ -93,6 +94,10 @@ export function listProcesses(rootPid: number): Promise { if (UTILITY_NETWORK_HINT.exec(cmd)) { return 'utility-network-service'; } + + if (UTILITY_EXTENSION_HOST_HINT.exec(cmd)) { + return 'extension-host'; + } } return matches[1]; } diff --git a/src/vs/platform/extensions/electron-main/extensionHostStarter.ts b/src/vs/platform/extensions/electron-main/extensionHostStarter.ts index ca52229c320..027748ea8c1 100644 --- a/src/vs/platform/extensions/electron-main/extensionHostStarter.ts +++ b/src/vs/platform/extensions/electron-main/extensionHostStarter.ts @@ -324,6 +324,7 @@ class UtilityExtensionHostProcess extends Disposable { const modulePath = FileAccess.asFileUri('bootstrap-fork.js', require).fsPath; const args: string[] = ['--type=extensionHost', '--skipWorkspaceStorageLock']; const execArgv: string[] = opts.execArgv || []; + execArgv.push(`--vscode-utility-kind=extensionHost`); const env: { [key: string]: any } = { ...opts.env }; // Make sure all values are strings, otherwise the process will not start From a1a0283b7a018c97caa524fd3045e7ce92f27ee0 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 4 Jul 2022 14:21:51 +0200 Subject: [PATCH 137/150] adopt #153865 (#154067) --- .../contrib/extensions/browser/extensionsViewlet.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index 6faf5d3c10a..31cc538fa56 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -158,7 +158,12 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio constructor() { super({ id: 'workbench.extensions.installLocalExtensions', - get title() { return localize('select and install local extensions', "Install Local Extensions in '{0}'...", server.label); }, + get title() { + return { + value: localize('select and install local extensions', "Install Local Extensions in '{0}'...", server.label), + original: `Install Local Extensions in '${server.label}'...`, + }; + }, category: localize({ key: 'remote', comment: ['Remote as in remote machine'] }, "Remote"), icon: installLocalInRemoteIcon, f1: true, From 496d4b2c1f62db770a832847ac64251d09d506d3 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 4 Jul 2022 14:24:56 +0200 Subject: [PATCH 138/150] enable import/export profiles actions in no profile mode (#154022) - enable actions - bring back old code to import profile --- .../common/userDataProfileActions.ts | 32 +++++++++++++++---- .../userDataProfile/common/userDataProfile.ts | 1 + .../userDataProfileImportExportService.ts | 22 +++++++++++-- 3 files changed, 46 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts b/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts index e796f1f897e..cda5ca4d8fe 100644 --- a/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts +++ b/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts @@ -7,8 +7,8 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { joinPath } from 'vs/base/common/resources'; import { localize } from 'vs/nls'; -import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; -import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IFileService } from 'vs/platform/files/common/files'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -19,6 +19,7 @@ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfile import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { CATEGORIES } from 'vs/workbench/common/actions'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; registerAction2(class CreateFromCurrentProfileAction extends Action2 { constructor() { @@ -196,14 +197,14 @@ registerAction2(class ExportProfileAction extends Action2 { original: 'Export Settings Profile...' }, category: PROFILES_CATEGORY, - f1: true, - precondition: PROFILES_ENABLEMENT_CONTEXT, menu: [ { id: ManageProfilesSubMenu, group: '3_import_export_profiles', when: PROFILES_ENABLEMENT_CONTEXT, order: 1 + }, { + id: MenuId.CommandPalette } ] }); @@ -241,14 +242,14 @@ registerAction2(class ImportProfileAction extends Action2 { original: 'Import Settings Profile...' }, category: PROFILES_CATEGORY, - f1: true, - precondition: PROFILES_ENABLEMENT_CONTEXT, menu: [ { id: ManageProfilesSubMenu, group: '3_import_export_profiles', when: PROFILES_ENABLEMENT_CONTEXT, order: 2 + }, { + id: MenuId.CommandPalette } ] }); @@ -260,6 +261,19 @@ registerAction2(class ImportProfileAction extends Action2 { const fileService = accessor.get(IFileService); const requestService = accessor.get(IRequestService); const userDataProfileImportExportService = accessor.get(IUserDataProfileImportExportService); + const dialogService = accessor.get(IDialogService); + const contextKeyService = accessor.get(IContextKeyService); + + const isSettingProfilesEnabled = contextKeyService.contextMatchesRules(PROFILES_ENABLEMENT_CONTEXT); + + if (!isSettingProfilesEnabled) { + if (!(await dialogService.confirm({ + title: localize('import profile title', "Import Settings from a Profile"), + message: localize('confiirmation message', "This will replace your current settings. Are you sure you want to continue?"), + })).confirmed) { + return; + } + } const disposables = new DisposableStore(); const quickPick = disposables.add(quickInputService.createQuickPick()); @@ -278,7 +292,11 @@ registerAction2(class ImportProfileAction extends Action2 { quickPick.hide(); const profile = quickPick.selectedItems[0].description ? await this.getProfileFromURL(quickPick.value, requestService) : await this.getProfileFromFileSystem(fileDialogService, fileService); if (profile) { - await userDataProfileImportExportService.importProfile(profile); + if (isSettingProfilesEnabled) { + await userDataProfileImportExportService.importProfile(profile); + } else { + await userDataProfileImportExportService.setProfile(profile); + } } })); disposables.add(quickPick.onDidHide(() => disposables.dispose())); diff --git a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts index a293ec712b1..bed76364445 100644 --- a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts +++ b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts @@ -60,6 +60,7 @@ export interface IUserDataProfileImportExportService { exportProfile(options?: ProfileCreationOptions): Promise; importProfile(profile: IUserDataProfileTemplate): Promise; + setProfile(profile: IUserDataProfileTemplate): Promise; } export interface IResourceProfile { diff --git a/src/vs/workbench/services/userDataProfile/common/userDataProfileImportExportService.ts b/src/vs/workbench/services/userDataProfile/common/userDataProfileImportExportService.ts index 7403079c1ef..8b9b4b76309 100644 --- a/src/vs/workbench/services/userDataProfile/common/userDataProfileImportExportService.ts +++ b/src/vs/workbench/services/userDataProfile/common/userDataProfileImportExportService.ts @@ -56,7 +56,7 @@ export class UserDataProfileImportExportService implements IUserDataProfileImpor await this.progressService.withProgress({ location: ProgressLocation.Notification, - title: localize('profiles.applying', "{0}: Importing...", PROFILES_CATEGORY), + title: localize('profiles.importing', "{0}: Importing...", PROFILES_CATEGORY), }, async progress => { await this.userDataProfileManagementService.createAndEnterProfile(name); if (profileTemplate.settings) { @@ -70,7 +70,25 @@ export class UserDataProfileImportExportService implements IUserDataProfileImpor } }); - this.notificationService.info(localize('applied profile', "{0}: Imported successfully.", PROFILES_CATEGORY)); + this.notificationService.info(localize('imported profile', "{0}: Imported successfully.", PROFILES_CATEGORY)); + } + + async setProfile(profile: IUserDataProfileTemplate): Promise { + await this.progressService.withProgress({ + location: ProgressLocation.Notification, + title: localize('profiles.applying', "{0}: Applying...", PROFILES_CATEGORY), + }, async progress => { + if (profile.settings) { + await this.settingsProfile.applyProfile(profile.settings); + } + if (profile.globalState) { + await this.globalStateProfile.applyProfile(profile.globalState); + } + if (profile.extensions) { + await this.extensionsProfile.applyProfile(profile.extensions); + } + }); + this.notificationService.info(localize('applied profile', "{0}: Applied successfully.", PROFILES_CATEGORY)); } } From 61188536ed77f5515346560db34f4046ab646d45 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 4 Jul 2022 14:32:58 +0200 Subject: [PATCH 139/150] debt - add `Event#fromObservable` to bridge between observables and emitters (#154069) --- src/vs/base/common/event.ts | 50 +++++++++++++++++++ src/vs/base/test/common/event.test.ts | 30 ++++++++++- .../mergeEditor/browser/mergeEditorInput.ts | 7 +-- 3 files changed, 81 insertions(+), 6 deletions(-) diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts index 647b5f1beed..5ceab5b92a0 100644 --- a/src/vs/base/common/event.ts +++ b/src/vs/base/common/event.ts @@ -8,6 +8,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { once as onceFn } from 'vs/base/common/functional'; import { combinedDisposable, Disposable, DisposableStore, IDisposable, SafeDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { LinkedList } from 'vs/base/common/linkedList'; +import { IObservable, IObserver } from 'vs/base/common/observable'; import { StopWatch } from 'vs/base/common/stopwatch'; @@ -423,6 +424,55 @@ export namespace Event { store?.dispose(); }); } + + class EmitterObserver implements IObserver { + + readonly emitter: Emitter; + + private _counter = 0; + private _hasChanged = false; + + constructor(readonly obs: IObservable, store: DisposableStore | undefined) { + const options = { + onFirstListenerAdd: () => { + obs.addObserver(this); + }, + onLastListenerRemove: () => { + obs.removeObserver(this); + } + }; + if (!store) { + _addLeakageTraceLogic(options); + } + this.emitter = new Emitter(options); + if (store) { + store.add(this.emitter); + } + } + + beginUpdate(_observable: IObservable): void { + // console.assert(_observable === this.obs); + this._counter++; + } + + handleChange(_observable: IObservable, _change: TChange): void { + this._hasChanged = true; + } + + endUpdate(_observable: IObservable): void { + if (--this._counter === 0) { + if (this._hasChanged) { + this._hasChanged = false; + this.emitter.fire(this.obs.get()); + } + } + } + } + + export function fromObservable(obs: IObservable, store?: DisposableStore): Event { + const observer = new EmitterObserver(obs, store); + return observer.emitter.event; + } } export interface EmitterOptions { diff --git a/src/vs/base/test/common/event.test.ts b/src/vs/base/test/common/event.test.ts index a34566c10f1..39d878bce5d 100644 --- a/src/vs/base/test/common/event.test.ts +++ b/src/vs/base/test/common/event.test.ts @@ -8,7 +8,8 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { errorHandler, setUnexpectedErrorHandler } from 'vs/base/common/errors'; import { AsyncEmitter, DebounceEmitter, Emitter, Event, EventBufferer, EventMultiplexer, IWaitUntil, MicrotaskEmitter, PauseableEmitter, Relay } from 'vs/base/common/event'; import { DisposableStore, IDisposable, isDisposable, setDisposableTracker, toDisposable } from 'vs/base/common/lifecycle'; -import { DisposableTracker } from 'vs/base/test/common/utils'; +import { observableValue, transaction } from 'vs/base/common/observable'; +import { DisposableTracker, ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; namespace Samples { @@ -624,6 +625,33 @@ suite('PausableEmitter', function () { }); }); +suite('Event utils - ensureNoDisposablesAreLeakedInTestSuite', function () { + ensureNoDisposablesAreLeakedInTestSuite(); + + test('fromObservable', function () { + + const obs = observableValue('test', 12); + const event = Event.fromObservable(obs); + + const values: number[] = []; + const d = event(n => { values.push(n); }); + + obs.set(3, undefined); + obs.set(13, undefined); + obs.set(3, undefined); + obs.set(33, undefined); + obs.set(1, undefined); + + transaction(tx => { + obs.set(334, tx); + obs.set(99, tx); + }); + + assert.deepStrictEqual(values, ([3, 13, 3, 33, 1, 99])); + d.dispose(); + }); +}); + suite('Event utils', () => { suite('EventBufferer', () => { diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts index 137f5a9b992..660d10cb487 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts @@ -17,12 +17,12 @@ import { IEditorIdentifier, IUntypedEditorInput } from 'vs/workbench/common/edit import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { AbstractTextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput'; import { EditorWorkerServiceDiffComputer } from 'vs/workbench/contrib/mergeEditor/browser/model/diffComputer'; -import { autorun } from 'vs/workbench/contrib/audioCues/browser/observable'; import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { ILanguageSupport, ITextFileEditorModel, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { assertType } from 'vs/base/common/types'; +import { Event } from 'vs/base/common/event'; export class MergeEditorInputData { constructor( @@ -123,10 +123,7 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements this._store.add(input2); this._store.add(result); - this._store.add(autorun(reader => { - this._model?.hasUnhandledConflicts.read(reader); - this._onDidChangeDirty.fire(undefined); - }, 'drive::onDidChangeDirty')); + this._store.add(Event.fromObservable(this._model.hasUnhandledConflicts)(() => this._onDidChangeDirty.fire(undefined))); } this._ignoreUnhandledConflictsForDirtyState = undefined; From 62ab77fa2127d17624f918745029b80206c1bfd6 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 4 Jul 2022 14:46:33 +0200 Subject: [PATCH 140/150] use `ICommandActionTitle` over simple string (#154081) https://github.com/microsoft/vscode/issues/153865 --- .../api/browser/mainThreadFileSystemEventService.ts | 5 ++++- .../inlayHints/browser/inlayHintsAccessibilty.ts | 10 ++++++++-- .../browser/languageStatus.contribution.ts | 10 ++++++++-- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts b/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts index 54a2887e33b..c7c01c717bf 100644 --- a/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts +++ b/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts @@ -185,7 +185,10 @@ registerAction2(class ResetMemento extends Action2 { constructor() { super({ id: 'files.participants.resetChoice', - title: localize('label', "Reset choice for 'File operation needs preview'"), + title: { + value: localize('label', "Reset choice for 'File operation needs preview'"), + original: `Reset choice for 'File operation needs preview'` + }, f1: true }); } diff --git a/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts b/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts index 2766516fdc0..e3d83192f51 100644 --- a/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts +++ b/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts @@ -174,7 +174,10 @@ registerAction2(class StartReadHints extends EditorAction2 { constructor() { super({ id: 'inlayHints.startReadingLineWithHint', - title: localize('read.title', 'Read Line With Inline Hints'), + title: { + value: localize('read.title', 'Read Line With Inline Hints'), + original: 'Read Line With Inline Hints' + }, precondition: EditorContextKeys.hasInlayHintsProvider, f1: true }); @@ -193,7 +196,10 @@ registerAction2(class StopReadHints extends EditorAction2 { constructor() { super({ id: 'inlayHints.stopReadingLineWithHint', - title: localize('stop.title', 'Stop Inlay Hints Reading'), + title: { + value: localize('stop.title', 'Stop Inlay Hints Reading'), + original: 'Stop Inlay Hints Reading' + }, precondition: InlayHintsAccessibility.IsReading, f1: true, keybinding: { diff --git a/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts b/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts index 661059e89b9..63b120d3b3c 100644 --- a/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts +++ b/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts @@ -398,8 +398,14 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'editor.inlayHints.Reset', - title: localize('reset', 'Reset Language Status Interaction Counter'), - category: localize('cat', 'View'), + title: { + value: localize('reset', 'Reset Language Status Interaction Counter'), + original: 'Reset Language Status Interaction Counter' + }, + category: { + value: localize('cat', 'View'), + original: 'View' + }, f1: true }); } From cdc1920447dc5d857ec947981f79a21674dcaa74 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 4 Jul 2022 14:53:00 +0200 Subject: [PATCH 141/150] introduce immediate update (#154082) --- .../contrib/output/browser/outputServices.ts | 2 +- .../output/common/outputChannelModel.ts | 28 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/contrib/output/browser/outputServices.ts b/src/vs/workbench/contrib/output/browser/outputServices.ts index c13ec6b5e0c..e9f97c01b29 100644 --- a/src/vs/workbench/contrib/output/browser/outputServices.ts +++ b/src/vs/workbench/contrib/output/browser/outputServices.ts @@ -48,7 +48,7 @@ class OutputChannel extends Disposable implements IOutputChannel { } update(mode: OutputChannelUpdateMode, till?: number): void { - this.model.update(mode, till); + this.model.update(mode, till, true); } clear(): void { diff --git a/src/vs/workbench/contrib/output/common/outputChannelModel.ts b/src/vs/workbench/contrib/output/common/outputChannelModel.ts index 4c01e67797c..58f8374650b 100644 --- a/src/vs/workbench/contrib/output/common/outputChannelModel.ts +++ b/src/vs/workbench/contrib/output/common/outputChannelModel.ts @@ -26,7 +26,7 @@ import { OutputChannelUpdateMode } from 'vs/workbench/services/output/common/out export interface IOutputChannelModel extends IDisposable { readonly onDispose: Event; append(output: string): void; - update(mode: OutputChannelUpdateMode, till?: number): void; + update(mode: OutputChannelUpdateMode, till: number | undefined, immediate: boolean): void; loadModel(): Promise; clear(): void; replace(value: string): void; @@ -129,12 +129,12 @@ export class FileOutputChannelModel extends Disposable implements IOutputChannel } clear(): void { - this.update(OutputChannelUpdateMode.Clear, this.endOffset); + this.update(OutputChannelUpdateMode.Clear, this.endOffset, true); } - update(mode: OutputChannelUpdateMode, till?: number): void { + update(mode: OutputChannelUpdateMode, till: number | undefined, immediate: boolean): void { const loadModelPromise: Promise = this.loadModelPromise ? this.loadModelPromise : Promise.resolve(); - loadModelPromise.then(() => this.doUpdate(mode, till)); + loadModelPromise.then(() => this.doUpdate(mode, till, immediate)); } loadModel(): Promise { @@ -174,7 +174,7 @@ export class FileOutputChannelModel extends Disposable implements IOutputChannel return this.model; } - private doUpdate(mode: OutputChannelUpdateMode, till?: number): void { + private doUpdate(mode: OutputChannelUpdateMode, till: number | undefined, immediate: boolean): void { if (mode === OutputChannelUpdateMode.Clear || mode === OutputChannelUpdateMode.Replace) { this.startOffset = this.endOffset = isNumber(till) ? till : this.endOffset; this.cancelModelUpdate(); @@ -198,7 +198,7 @@ export class FileOutputChannelModel extends Disposable implements IOutputChannel } else { - this.appendContent(this.model, token); + this.appendContent(this.model, immediate, token); } } @@ -206,7 +206,7 @@ export class FileOutputChannelModel extends Disposable implements IOutputChannel this.doUpdateModel(model, [EditOperation.delete(model.getFullModelRange())], VSBuffer.fromString('')); } - private async appendContent(model: ITextModel, token: CancellationToken): Promise { + private async appendContent(model: ITextModel, immediate: boolean, token: CancellationToken): Promise { this.appendThrottler.trigger(async () => { /* Abort if operation is cancelled */ if (token.isCancellationRequested) { @@ -234,7 +234,7 @@ export class FileOutputChannelModel extends Disposable implements IOutputChannel const lastLineMaxColumn = model.getLineMaxColumn(lastLine); const edits = [EditOperation.insert(new Position(lastLine, lastLineMaxColumn), contentToAppend.toString())]; this.doUpdateModel(model, edits, contentToAppend); - }); + }, immediate ? 0 : undefined); } private async replaceContent(model: ITextModel, token: CancellationToken): Promise { @@ -298,10 +298,10 @@ export class FileOutputChannelModel extends Disposable implements IOutputChannel if (!this.modelUpdateInProgress) { if (isNumber(size) && this.endOffset > size) { // Reset - Content is removed - this.update(OutputChannelUpdateMode.Clear, 0); + this.update(OutputChannelUpdateMode.Clear, 0, true); } } - this.update(OutputChannelUpdateMode.Append); + this.update(OutputChannelUpdateMode.Append, undefined, false /* Not needed to update immediately. Wait to collect more changes and update. */); } } @@ -340,13 +340,13 @@ class OutputChannelBackedByFile extends FileOutputChannelModel implements IOutpu override append(message: string): void { this.write(message); - this.update(OutputChannelUpdateMode.Append); + this.update(OutputChannelUpdateMode.Append, undefined, this.isVisible()); } override replace(message: string): void { const till = this._offset; this.write(message); - this.update(OutputChannelUpdateMode.Replace, till); + this.update(OutputChannelUpdateMode.Replace, till, true); } private write(content: string): void { @@ -391,8 +391,8 @@ export class DelegatedOutputChannelModel extends Disposable implements IOutputCh this.outputChannelModel.then(outputChannelModel => outputChannelModel.append(output)); } - update(mode: OutputChannelUpdateMode, till?: number): void { - this.outputChannelModel.then(outputChannelModel => outputChannelModel.update(mode, till)); + update(mode: OutputChannelUpdateMode, till: number | undefined, immediate: boolean): void { + this.outputChannelModel.then(outputChannelModel => outputChannelModel.update(mode, till, immediate)); } loadModel(): Promise { From 428c8e29bd6b18729e4ba3c6af971f8db9eb8694 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 4 Jul 2022 15:37:48 +0200 Subject: [PATCH 142/150] SCM - More focus related fixes to the commit action button (#154087) More focus related fixes to the commit action button --- src/vs/base/browser/ui/list/listWidget.ts | 16 ++++++++++++++++ src/vs/base/browser/ui/tree/abstractTree.ts | 6 ++++-- .../workbench/contrib/scm/browser/scmViewPane.ts | 2 ++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 20df7f37ba8..d97fe00bbf2 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -258,6 +258,22 @@ export function isMonacoEditor(e: HTMLElement): boolean { return isMonacoEditor(e.parentElement); } +export function isButton(e: HTMLElement): boolean { + if (e.tagName === 'A' && e.classList.contains('monaco-button')) { + return true; + } + + if (e.classList.contains('monaco-list')) { + return false; + } + + if (!e.parentElement) { + return false; + } + + return isButton(e.parentElement); +} + class KeyboardController implements IDisposable { private readonly disposables = new DisposableStore(); diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index c03f5c5e1af..124d926541f 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -9,7 +9,7 @@ import { DomEmitter } from 'vs/base/browser/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IIdentityProvider, IKeyboardNavigationDelegate, IKeyboardNavigationLabelProvider, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IListMouseEvent, IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; -import { DefaultKeyboardNavigationDelegate, IListOptions, IListStyles, isInputElement, isMonacoEditor, List, MouseController } from 'vs/base/browser/ui/list/listWidget'; +import { DefaultKeyboardNavigationDelegate, IListOptions, IListStyles, isButton, isInputElement, isMonacoEditor, List, MouseController } from 'vs/base/browser/ui/list/listWidget'; import { getVisibleState, isFilterResult } from 'vs/base/browser/ui/tree/indexTreeModel'; import { ICollapseStateChangeEvent, ITreeContextMenuEvent, ITreeDragAndDrop, ITreeEvent, ITreeFilter, ITreeModel, ITreeModelSpliceEvent, ITreeMouseEvent, ITreeNavigator, ITreeNode, ITreeRenderer, TreeDragOverBubble, TreeError, TreeFilterResult, TreeMouseEventTarget, TreeVisibility } from 'vs/base/browser/ui/tree/tree'; import { distinct, equals, firstOrDefault, range } from 'vs/base/common/arrays'; @@ -1153,7 +1153,9 @@ class TreeNodeListMouseController extends MouseController< } protected override onViewPointer(e: IListMouseEvent>): void { - if (isInputElement(e.browserEvent.target as HTMLElement) || isMonacoEditor(e.browserEvent.target as HTMLElement)) { + if (isButton(e.browserEvent.target as HTMLElement) || + isInputElement(e.browserEvent.target as HTMLElement) || + isMonacoEditor(e.browserEvent.target as HTMLElement)) { return; } diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 47801f55fd9..41cce292993 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -2400,6 +2400,7 @@ export class SCMViewPane extends ViewPane { if (widget) { widget.focus(); + this.tree.setFocus([], e.browserEvent); const selection = this.tree.getSelection(); @@ -2416,6 +2417,7 @@ export class SCMViewPane extends ViewPane { const target = e.browserEvent?.target as HTMLElement; if (target.classList.contains('monaco-tl-row') || target.classList.contains('button-container')) { this.actionButtonRenderer.focusActionButton(e.element); + this.tree.setFocus([], e.browserEvent); } return; From d63c49ea080b51b65b73d348f2a09432ed4caab7 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 4 Jul 2022 15:38:09 +0200 Subject: [PATCH 143/150] push workaround for https://github.com/microsoft/vscode/issues/154083 (#154084) --- src/vs/base/common/event.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts index 5ceab5b92a0..d395e4a9f32 100644 --- a/src/vs/base/common/event.ts +++ b/src/vs/base/common/event.ts @@ -8,7 +8,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { once as onceFn } from 'vs/base/common/functional'; import { combinedDisposable, Disposable, DisposableStore, IDisposable, SafeDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { LinkedList } from 'vs/base/common/linkedList'; -import { IObservable, IObserver } from 'vs/base/common/observable'; +import { IObservable, IObserver } from 'vs/base/common/observableImpl/base'; import { StopWatch } from 'vs/base/common/stopwatch'; From e2209cb03d3d542075785aef89b6e5b6c8c719fa Mon Sep 17 00:00:00 2001 From: Johannes Date: Mon, 4 Jul 2022 15:39:48 +0200 Subject: [PATCH 144/150] make sure focus outline fits into CC box fixes https://github.com/microsoft/vscode/issues/153763 --- .../browser/parts/titlebar/media/titlebarpart.css | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index b661db98d5c..b0452d28eb2 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -101,8 +101,8 @@ color: var(--vscode-commandCenter-foreground); background-color: var(--vscode-commandCenter-background); border: 1px solid var(--vscode-commandCenter-border); - border-radius: 5px; - height: 20px; + border-radius: 6px; + height: 22px; line-height: 18px; width: 38vw; max-width: 600px; @@ -119,9 +119,9 @@ line-height: 18px; } -.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center:HOVER .quickopen .action-label { - background-color: transparent !important; - outline-color: transparent !important; +.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.quickopen>.action-label { + height: 16px; + line-height: 16px; } .monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.quickopen>.action-label.search { From b2d1f531341fa9416a7f5d3c8ce2226e0e43e8b8 Mon Sep 17 00:00:00 2001 From: Johannes Date: Mon, 4 Jul 2022 16:34:20 +0200 Subject: [PATCH 145/150] make CC be two special `MenuEntryActionViewItem` instances and style them as one fixes https://github.com/microsoft/vscode/issues/153762 --- .../parts/titlebar/commandCenterControl.ts | 55 ++++++++++------- .../parts/titlebar/media/titlebarpart.css | 61 ++++++++++--------- 2 files changed, 65 insertions(+), 51 deletions(-) diff --git a/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts b/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts index de2f74a7b2f..cc6278c3a34 100644 --- a/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts @@ -4,21 +4,20 @@ *--------------------------------------------------------------------------------------------*/ import { reset } from 'vs/base/browser/dom'; -import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; -import { Action, IAction } from 'vs/base/common/actions'; +import { IAction } from 'vs/base/common/actions'; import { Codicon } from 'vs/base/common/codicons'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { assertType } from 'vs/base/common/types'; import { localize } from 'vs/nls'; import { createActionViewItem, createAndFillInContextMenuActions, MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { Action2, IMenuService, MenuId, MenuItemAction, registerAction2 } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import * as colors from 'vs/platform/theme/common/colorRegistry'; @@ -57,7 +56,7 @@ export class CommandCenterControl { override render(container: HTMLElement): void { super.render(container); - container.classList.add('quickopen'); + container.classList.add('quickopen', 'left'); assertType(this.label); this.label.classList.add('search'); @@ -68,7 +67,7 @@ export class CommandCenterControl { this.workspaceTitle.classList.add('search-label'); this._updateFromWindowTitle(); reset(this.label, searchIcon, this.workspaceTitle); - this._renderAllQuickPickItem(container); + // this._renderAllQuickPickItem(container); this._store.add(windowTitle.onDidChange(this._updateFromWindowTitle, this)); } @@ -96,24 +95,23 @@ export class CommandCenterControl { : localize('title2', "Search {0} \u2014 {1}", windowTitle.workspaceName, windowTitle.value); this._applyUpdateTooltip(title); } - - private _renderAllQuickPickItem(parent: HTMLElement): void { - const container = document.createElement('span'); - container.classList.add('all-options'); - parent.appendChild(container); - const action = new Action('all', localize('all', "Show Search Modes..."), Codicon.chevronDown.classNames, true, () => { - quickInputService.quickAccess.show('?'); - }); - const dropdown = new ActionViewItem(undefined, action, { icon: true, label: false, hoverDelegate }); - dropdown.render(container); - this._store.add(dropdown); - this._store.add(action); - } } return instantiationService.createInstance(InputLikeViewItem, action, { hoverDelegate }); - } - return createActionViewItem(instantiationService, action, { hoverDelegate }); + } else if (action instanceof MenuItemAction && action.id === 'commandCenter.help') { + + class ExtraClass extends MenuEntryActionViewItem { + override render(container: HTMLElement): void { + super.render(container); + container.classList.add('quickopen', 'right'); + } + } + + return instantiationService.createInstance(ExtraClass, action, { hoverDelegate }); + + } else { + return createActionViewItem(instantiationService, action, { hoverDelegate }); + } } }); const menu = this._disposables.add(menuService.createMenu(MenuId.CommandCenter, contextKeyService)); @@ -143,6 +141,21 @@ export class CommandCenterControl { } } +registerAction2(class extends Action2 { + + constructor() { + super({ + id: 'commandCenter.help', + title: localize('all', "Show Search Modes..."), + icon: Codicon.chevronDown, + menu: { id: MenuId.CommandCenter, order: 100 } + }); + } + run(accessor: ServicesAccessor): void { + accessor.get(IQuickInputService).quickAccess.show('?'); + } +}); + // --- theme colors // foreground (inactive and active) diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index b0452d28eb2..29f1142364e 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -101,13 +101,6 @@ color: var(--vscode-commandCenter-foreground); background-color: var(--vscode-commandCenter-background); border: 1px solid var(--vscode-commandCenter-border); - border-radius: 6px; - height: 22px; - line-height: 18px; - width: 38vw; - max-width: 600px; - min-width: 32px; - margin: 0 4px; flex-direction: row; justify-content: center; overflow: hidden; @@ -116,47 +109,55 @@ .monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.quickopen:HOVER { color: var(--vscode-commandCenter-activeForeground); background-color: var(--vscode-commandCenter-activeBackground); - line-height: 18px; +} + +.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.quickopen.left { + /* border,margin tricks */ + margin-left: 6px; + border-top-left-radius: 6px; + border-bottom-left-radius: 6px; + border-right: none; + + /* width */ + width: 38vw; + max-width: 600px; + min-width: 32px; +} + +.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.quickopen.right { + /* border,margin tricks */ + margin-right: 6px; + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; + border-left: none; + + /* width */ + width: 16px; } .monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.quickopen>.action-label { - height: 16px; - line-height: 16px; -} - -.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.quickopen>.action-label.search { + height: 22px; + line-height: 22px; + padding: 0; + background-color: transparent; display: inline-flex; text-align: center; font-size: 12px; - background-color: inherit; justify-content: center; - width: calc(100% - 19px); + width: 100%; } -.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.quickopen>.action-label.search>.search-icon { +.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.quickopen.left>.action-label.search>.search-icon { font-size: 14px; opacity: .8; margin: auto 3px; } -.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.quickopen>.action-label.search>.search-label { +.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.quickopen.left>.action-label.search>.search-label { overflow: hidden; text-overflow: ellipsis; } -.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.quickopen>.all-options>.action-label { - text-align: center; - font-size: 12px; - width: 16px; - border-left: 1px solid transparent; - border-radius: 0; - padding-right: 0; -} - -.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center:HOVER .action-item.quickopen>.all-options>.action-label { - border-color: var(--vscode-commandCenter-border); -} - /* Menubar */ .monaco-workbench .part.titlebar>.titlebar-container>.menubar { /* move menubar above drag region as negative z-index on drag region cause greyscale AA */ From 6e3e8aa68822662dd5de879e060016e5aeaf978f Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 4 Jul 2022 16:35:37 +0200 Subject: [PATCH 146/150] Fix #153730 (#154093) --- .../platform/files/browser/indexedDBFileSystemProvider.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/platform/files/browser/indexedDBFileSystemProvider.ts b/src/vs/platform/files/browser/indexedDBFileSystemProvider.ts index 36b2f65cdfc..af4458fe1c0 100644 --- a/src/vs/platform/files/browser/indexedDBFileSystemProvider.ts +++ b/src/vs/platform/files/browser/indexedDBFileSystemProvider.ts @@ -253,7 +253,7 @@ export class IndexedDBFileSystemProvider extends Disposable implements IFileSyst private readonly _onReportError = this._register(new Emitter()); readonly onReportError = this._onReportError.event; - private readonly versions = new Map(); + private readonly mtimes = new Map(); private cachedFiletree: Promise | undefined; private writeManyThrottler: Throttler; @@ -289,7 +289,7 @@ export class IndexedDBFileSystemProvider extends Disposable implements IFileSyst return { type: FileType.File, ctime: 0, - mtime: this.versions.get(resource.toString()) || 0, + mtime: this.mtimes.get(resource.toString()) || 0, size: entry.size ?? (await this.readFile(resource)).byteLength }; } @@ -434,7 +434,7 @@ export class IndexedDBFileSystemProvider extends Disposable implements IFileSyst } await this.deleteKeys(toDelete); (await this.getFiletree()).delete(resource.path); - toDelete.forEach(key => this.versions.delete(key)); + toDelete.forEach(key => this.mtimes.delete(key)); this.triggerChanges(toDelete.map(path => ({ resource: resource.with({ path }), type: FileChangeType.DELETED }))); } @@ -487,7 +487,7 @@ export class IndexedDBFileSystemProvider extends Disposable implements IFileSyst const fileTree = await this.getFiletree(); for (const [resource, content] of files) { fileTree.add(resource.path, { type: 'file', size: content.byteLength }); - this.versions.set(resource.toString(), (this.versions.get(resource.toString()) || 0) + 1); + this.mtimes.set(resource.toString(), Date.now()); } this.triggerChanges(files.map(([resource]) => ({ resource, type: FileChangeType.UPDATED }))); From 11d625ee1ff47bb46121af63ef6a0bb4f96cd9bd Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 4 Jul 2022 16:35:58 +0200 Subject: [PATCH 147/150] completes observable refactoring (#154089) --- src/vs/base/common/observableImpl/base.ts | 2 +- .../browser/audioCueDebuggerContribution.ts | 2 +- .../audioCueLineFeatureContribution.ts | 26 ++++-------- .../audioCues/browser/audioCueService.ts | 4 +- .../contrib/audioCues/browser/observable.ts | 40 ------------------- .../browser/model/mergeEditorModel.ts | 24 +++++------ .../browser/model/textModelDiffs.ts | 6 +-- .../contrib/mergeEditor/browser/utils.ts | 9 ++--- .../mergeEditor/browser/view/editorGutter.ts | 9 ++--- .../browser/view/editors/codeEditorView.ts | 4 +- .../view/editors/inputCodeEditorView.ts | 13 +++--- .../view/editors/resultCodeEditorView.ts | 8 ++-- .../mergeEditor/browser/view/mergeEditor.ts | 2 +- .../mergeEditor/browser/view/viewModel.ts | 8 ++-- .../mergeEditor/test/browser/model.test.ts | 2 +- 15 files changed, 53 insertions(+), 106 deletions(-) delete mode 100644 src/vs/workbench/contrib/audioCues/browser/observable.ts diff --git a/src/vs/base/common/observableImpl/base.ts b/src/vs/base/common/observableImpl/base.ts index fd91e6f8d8d..9d83083e49b 100644 --- a/src/vs/base/common/observableImpl/base.ts +++ b/src/vs/base/common/observableImpl/base.ts @@ -196,7 +196,7 @@ export class TransactionImpl implements ITransaction { export interface ISettableObservable extends IObservable, ISettable { } -export function observableValue(name: string, initialValue: T): ISettableObservable { +export function observableValue(name: string, initialValue: T): ISettableObservable { return new ObservableValue(name, initialValue); } diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts index fc3727f804d..3b400d3fafc 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { autorunWithStore } from 'vs/base/common/observable'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { AudioCue, IAudioCueService } from 'vs/workbench/contrib/audioCues/browser/audioCueService'; -import { autorunWithStore } from 'vs/workbench/contrib/audioCues/browser/observable'; import { IDebugService, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; export class AudioCueLineDebuggerContribution diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts index edbdd468675..490fab6c4c6 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts @@ -12,21 +12,11 @@ import { ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/edito import { IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/markers'; import { FoldingController } from 'vs/editor/contrib/folding/browser/folding'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { - autorun, - autorunDelta, - constObservable, - derivedObservable, - observableFromEvent, - observableFromPromise, - IObservable, - wasEventTriggeredRecently, - debouncedObservable, -} from 'vs/workbench/contrib/audioCues/browser/observable'; import { ITextModel } from 'vs/editor/common/model'; import { GhostTextController } from 'vs/editor/contrib/inlineCompletions/browser/ghostTextController'; import { AudioCue, IAudioCueService } from 'vs/workbench/contrib/audioCues/browser/audioCueService'; import { CursorChangeReason } from 'vs/editor/common/cursorEvents'; +import { autorun, autorunDelta, constObservable, debouncedObservable, derived, IObservable, observableFromEvent, observableFromPromise, wasEventTriggeredRecently } from 'vs/base/common/observable'; export class AudioCueLineFeatureContribution extends Disposable @@ -48,7 +38,7 @@ export class AudioCueLineFeatureContribution ) { super(); - const someAudioCueFeatureIsEnabled = derivedObservable( + const someAudioCueFeatureIsEnabled = derived( 'someAudioCueFeatureIsEnabled', (reader) => this.features.some((feature) => @@ -73,7 +63,7 @@ export class AudioCueLineFeatureContribution ); this._register( - autorun((reader) => { + autorun('updateAudioCuesEnabled', (reader) => { this.store.clear(); if (!someAudioCueFeatureIsEnabled.read(reader)) { @@ -84,7 +74,7 @@ export class AudioCueLineFeatureContribution if (activeEditor) { this.registerAudioCuesForEditor(activeEditor.editor, activeEditor.model, this.store); } - }, 'updateAudioCuesEnabled') + }) ); } @@ -118,7 +108,7 @@ export class AudioCueLineFeatureContribution const featureStates = this.features.map((feature) => { const lineFeatureState = feature.getObservableState(editor, editorModel); - const isFeaturePresent = derivedObservable( + const isFeaturePresent = derived( `isPresentInLine:${feature.audioCue.name}`, (reader) => { if (!this.audioCueService.isEnabled(feature.audioCue).read(reader)) { @@ -130,7 +120,7 @@ export class AudioCueLineFeatureContribution : lineFeatureState.read(reader).isPresent(lineNumber); } ); - return derivedObservable( + return derived( `typingDebouncedFeatureState:\n${feature.audioCue.name}`, (reader) => feature.debounceWhileTyping && isTyping.read(reader) @@ -139,7 +129,7 @@ export class AudioCueLineFeatureContribution ); }); - const state = derivedObservable( + const state = derived( 'states', (reader) => ({ lineNumber: debouncedLineNumber.read(reader), @@ -282,7 +272,7 @@ class InlineCompletionLineFeature implements LineFeature { : undefined )); - return derivedObservable('ghostText', reader => { + return derived('ghostText', reader => { const ghostText = activeGhostText.read(reader)?.read(reader); return { isPresent(lineNumber) { diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCueService.ts b/src/vs/workbench/contrib/audioCues/browser/audioCueService.ts index f313933a8ec..4e491840f5a 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCueService.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCueService.ts @@ -9,9 +9,9 @@ import { FileAccess } from 'vs/base/common/network'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { observableFromEvent, IObservable, derivedObservable } from 'vs/workbench/contrib/audioCues/browser/observable'; import { Event } from 'vs/base/common/event'; import { localize } from 'vs/nls'; +import { IObservable, observableFromEvent, derived } from 'vs/base/common/observable'; export const IAudioCueService = createDecorator('audioCue'); @@ -95,7 +95,7 @@ export class AudioCueService extends Disposable implements IAudioCueService { ), () => this.configurationService.getValue<'on' | 'off' | 'auto'>(cue.settingsKey) ); - return derivedObservable('audio cue enabled', reader => { + return derived('audio cue enabled', reader => { const setting = settingObservable.read(reader); if ( setting === 'on' || diff --git a/src/vs/workbench/contrib/audioCues/browser/observable.ts b/src/vs/workbench/contrib/audioCues/browser/observable.ts deleted file mode 100644 index 37d2f8a97c8..00000000000 --- a/src/vs/workbench/contrib/audioCues/browser/observable.ts +++ /dev/null @@ -1,40 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IDisposable } from 'vs/base/common/lifecycle'; -import * as observable from 'vs/base/common/observable'; -export { - observableFromEvent, - autorunWithStore, - IObservable, - transaction, - ITransaction, - autorunDelta, - constObservable, - observableFromPromise, - wasEventTriggeredRecently, - debouncedObservable, - autorunHandleChanges, - waitForState, - keepAlive, - IReader, - derivedObservableWithCache, - derivedObservableWithWritableCache, -} from 'vs/base/common/observable'; -import * as observableValue from 'vs/base/common/observableImpl/base'; - -export function autorun(fn: (reader: observable.IReader) => void, name: string): IDisposable { - return observable.autorun(name, fn); -} - -export class ObservableValue extends observableValue.ObservableValue { - constructor(initialValue: T, name: string) { - super(name, initialValue); - } -} - -export function derivedObservable(name: string, computeFn: (reader: observable.IReader) => T): observable.IObservable { - return observable.derived(name, computeFn); -} diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts index 721cd75e3d1..6bf21e30d44 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts @@ -5,11 +5,11 @@ import { CompareResult, equals } from 'vs/base/common/arrays'; import { BugIndicatingError } from 'vs/base/common/errors'; +import { ISettableObservable, derived, waitForState, observableValue, keepAlive, autorunHandleChanges, transaction, IReader, ITransaction, IObservable } from 'vs/base/common/observable'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { ITextModel, ITextSnapshot } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; import { EditorModel } from 'vs/workbench/common/editor/editorModel'; -import { autorunHandleChanges, derivedObservable, IObservable, IReader, ITransaction, keepAlive, ObservableValue, transaction, waitForState } from 'vs/workbench/contrib/audioCues/browser/observable'; import { IDiffComputer } from 'vs/workbench/contrib/mergeEditor/browser/model/diffComputer'; import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange'; import { DetailedLineRangeMapping, DocumentMapping, LineRangeMapping } from 'vs/workbench/contrib/mergeEditor/browser/model/mapping'; @@ -28,7 +28,7 @@ export class MergeEditorModel extends EditorModel { private readonly input2TextModelDiffs = this._register(new TextModelDiffs(this.base, this.input2, this.diffComputer)); private readonly resultTextModelDiffs = this._register(new TextModelDiffs(this.base, this.result, this.diffComputer)); - public readonly state = derivedObservable('state', reader => { + public readonly state = derived('state', reader => { const states = [ this.input1TextModelDiffs, this.input2TextModelDiffs, @@ -44,11 +44,11 @@ export class MergeEditorModel extends EditorModel { return MergeEditorModelState.upToDate; }); - public readonly isUpToDate = derivedObservable('isUpToDate', reader => this.state.read(reader) === MergeEditorModelState.upToDate); + public readonly isUpToDate = derived('isUpToDate', reader => this.state.read(reader) === MergeEditorModelState.upToDate); public readonly onInitialized = waitForState(this.state, state => state === MergeEditorModelState.upToDate); - public readonly modifiedBaseRanges = derivedObservable('modifiedBaseRanges', (reader) => { + public readonly modifiedBaseRanges = derived('modifiedBaseRanges', (reader) => { const input1Diffs = this.input1TextModelDiffs.diffs.read(reader); const input2Diffs = this.input2TextModelDiffs.diffs.read(reader); @@ -60,22 +60,22 @@ export class MergeEditorModel extends EditorModel { public readonly resultDiffs = this.resultTextModelDiffs.diffs; private readonly modifiedBaseRangeStateStores = - derivedObservable('modifiedBaseRangeStateStores', reader => { + derived('modifiedBaseRangeStateStores', reader => { const map = new Map( - this.modifiedBaseRanges.read(reader).map(s => ([s, new ObservableValue(ModifiedBaseRangeState.default, 'State')])) + this.modifiedBaseRanges.read(reader).map(s => ([s, observableValue('State', ModifiedBaseRangeState.default)])) ); return map; }); private readonly modifiedBaseRangeHandlingStateStores = - derivedObservable('modifiedBaseRangeHandlingStateStores', reader => { + derived('modifiedBaseRangeHandlingStateStores', reader => { const map = new Map( - this.modifiedBaseRanges.read(reader).map(s => ([s, new ObservableValue(false, 'State')])) + this.modifiedBaseRanges.read(reader).map(s => ([s, observableValue('State', false)])) ); return map; }); - public readonly unhandledConflictsCount = derivedObservable('unhandledConflictsCount', reader => { + public readonly unhandledConflictsCount = derived('unhandledConflictsCount', reader => { const map = this.modifiedBaseRangeHandlingStateStores.read(reader); let handledCount = 0; for (const [_key, value] of map) { @@ -86,7 +86,7 @@ export class MergeEditorModel extends EditorModel { public readonly hasUnhandledConflicts = this.unhandledConflictsCount.map(value => /** @description hasUnhandledConflicts */ value > 0); - public readonly input1ResultMapping = derivedObservable('input1ResultMapping', reader => { + public readonly input1ResultMapping = derived('input1ResultMapping', reader => { const resultDiffs = this.resultDiffs.read(reader); const modifiedBaseRanges = DocumentMapping.betweenOutputs(this.input1LinesDiffs.read(reader), resultDiffs, this.input1.getLineCount()); @@ -103,7 +103,7 @@ export class MergeEditorModel extends EditorModel { ); }); - public readonly input2ResultMapping = derivedObservable('input2ResultMapping', reader => { + public readonly input2ResultMapping = derived('input2ResultMapping', reader => { const resultDiffs = this.resultDiffs.read(reader); const modifiedBaseRanges = DocumentMapping.betweenOutputs(this.input2LinesDiffs.read(reader), resultDiffs, this.input2.getLineCount()); @@ -192,7 +192,7 @@ export class MergeEditorModel extends EditorModel { return this.resultTextModelDiffs.getResultRange(baseRange, reader); } - private recomputeState(resultDiffs: DetailedLineRangeMapping[], stores: Map>, tx: ITransaction): void { + private recomputeState(resultDiffs: DetailedLineRangeMapping[], stores: Map>, tx: ITransaction): void { const baseRangeWithStoreAndTouchingDiffs = leftJoin( stores, resultDiffs, diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts index ed894cc7e25..af21adbebb5 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts @@ -7,17 +7,17 @@ import { compareBy, numberComparator } from 'vs/base/common/arrays'; import { BugIndicatingError } from 'vs/base/common/errors'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { ITextModel } from 'vs/editor/common/model'; -import { IObservable, IReader, ITransaction, ObservableValue, transaction } from 'vs/workbench/contrib/audioCues/browser/observable'; import { DetailedLineRangeMapping } from 'vs/workbench/contrib/mergeEditor/browser/model/mapping'; import { LineRangeEdit } from 'vs/workbench/contrib/mergeEditor/browser/model/editing'; import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange'; import { ReentrancyBarrier } from 'vs/workbench/contrib/mergeEditor/browser/utils'; import { IDiffComputer } from './diffComputer'; +import { IObservable, IReader, ITransaction, observableValue, transaction } from 'vs/base/common/observable'; export class TextModelDiffs extends Disposable { private updateCount = 0; - private readonly _state = new ObservableValue(TextModelDiffState.initializing, 'LiveDiffState'); - private readonly _diffs = new ObservableValue([], 'LiveDiffs'); + private readonly _state = observableValue('LiveDiffState', TextModelDiffState.initializing); + private readonly _diffs = observableValue('LiveDiffs', []); private readonly barrier = new ReentrancyBarrier(); private isDisposed = false; diff --git a/src/vs/workbench/contrib/mergeEditor/browser/utils.ts b/src/vs/workbench/contrib/mergeEditor/browser/utils.ts index 3188a3b2e7a..7e92f970387 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/utils.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/utils.ts @@ -5,11 +5,10 @@ import { CompareResult, ArrayQueue } from 'vs/base/common/arrays'; import { BugIndicatingError } from 'vs/base/common/errors'; -import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IObservable, autorun } from 'vs/base/common/observable'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { IModelDeltaDecoration } from 'vs/editor/common/model'; -import { IObservable, autorun } from 'vs/workbench/contrib/audioCues/browser/observable'; -import { IDisposable } from 'xterm'; export class ReentrancyBarrier { private isActive = false; @@ -74,12 +73,12 @@ function toSize(value: number | string): string { export function applyObservableDecorations(editor: CodeEditorWidget, decorations: IObservable): IDisposable { const d = new DisposableStore(); let decorationIds: string[] = []; - d.add(autorun(reader => { + d.add(autorun(`Apply decorations from ${decorations.debugName}`, reader => { const d = decorations.read(reader); editor.changeDecorations(a => { decorationIds = a.deltaDecorations(decorationIds, d); }); - }, `Apply decorations from ${decorations.debugName}`)); + })); d.add({ dispose: () => { editor.changeDecorations(a => { diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts index 0a54f686ca4..7289cc61637 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts @@ -5,9 +5,8 @@ import { h } from 'vs/base/browser/dom'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; -import { observableSignalFromEvent } from 'vs/base/common/observable'; +import { autorun, IReader, observableFromEvent, observableSignalFromEvent } from 'vs/base/common/observable'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; -import { autorun, IReader, observableFromEvent } from 'vs/workbench/contrib/audioCues/browser/observable'; import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange'; export class EditorGutter extends Disposable { @@ -36,11 +35,11 @@ export class EditorGutter extends D .root ); - this._register(autorun((reader) => { + this._register(autorun('update scroll decoration', (reader) => { scrollDecoration.className = this.isScrollTopZero.read(reader) ? '' : 'scroll-decoration'; - }, 'update scroll decoration')); + })); - this._register(autorun((reader) => this.render(reader), 'EditorGutter.Render')); + this._register(autorun('EditorGutter.Render', (reader) => this.render(reader))); } private readonly views = new Map(); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts index f843553c33b..f6a409656f8 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts @@ -8,18 +8,18 @@ import { IView, IViewSize } from 'vs/base/browser/ui/grid/grid'; import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; +import { IObservable, observableFromEvent, observableValue, transaction } from 'vs/base/common/observable'; import { IEditorContributionDescription } from 'vs/editor/browser/editorExtensions'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { ITextModel } from 'vs/editor/common/model'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { DEFAULT_EDITOR_MAX_DIMENSIONS, DEFAULT_EDITOR_MIN_DIMENSIONS } from 'vs/workbench/browser/parts/editor/editor'; -import { IObservable, observableFromEvent, ObservableValue, transaction } from 'vs/workbench/contrib/audioCues/browser/observable'; import { setStyle } from 'vs/workbench/contrib/mergeEditor/browser/utils'; import { MergeEditorViewModel } from 'vs/workbench/contrib/mergeEditor/browser/view/viewModel'; export abstract class CodeEditorView extends Disposable { - private readonly _viewModel = new ObservableValue(undefined, 'viewModel'); + private readonly _viewModel = observableValue('viewModel', undefined); readonly viewModel: IObservable = this._viewModel; readonly model = this._viewModel.map(m => /** @description model */ m?.model); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts index 476e5143570..abadef681ba 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts @@ -8,7 +8,7 @@ import { Toggle } from 'vs/base/browser/ui/toggle/toggle'; import { Action, IAction, Separator } from 'vs/base/common/actions'; import { Codicon } from 'vs/base/common/codicons'; import { Disposable } from 'vs/base/common/lifecycle'; -import { derived, ISettableObservable } from 'vs/base/common/observable'; +import { autorun, derived, IObservable, ISettableObservable, ITransaction, transaction, observableValue } from 'vs/base/common/observable'; import { noBreakWhitespace } from 'vs/base/common/strings'; import { isDefined } from 'vs/base/common/types'; import { EditorExtensionsRegistry, IEditorContributionDescription } from 'vs/editor/browser/editorExtensions'; @@ -19,7 +19,6 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { attachToggleStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { autorun, derivedObservable, IObservable, ITransaction, ObservableValue, transaction } from 'vs/workbench/contrib/audioCues/browser/observable'; import { InputState, ModifiedBaseRangeState } from 'vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange'; import { applyObservableDecorations, setFields } from 'vs/workbench/contrib/mergeEditor/browser/utils'; import { handledConflictMinimapOverViewRulerColor, unhandledConflictMinimapOverViewRulerColor } from 'vs/workbench/contrib/mergeEditor/browser/view/colors'; @@ -27,7 +26,7 @@ import { EditorGutter, IGutterItemInfo, IGutterItemView } from '../editorGutter' import { CodeEditorView } from './codeEditorView'; export class InputCodeEditorView extends CodeEditorView { - private readonly decorations = derivedObservable(`input${this.inputNumber}.decorations`, reader => { + private readonly decorations = derived(`input${this.inputNumber}.decorations`, reader => { const viewModel = this.viewModel.read(reader); if (!viewModel) { return []; @@ -112,7 +111,7 @@ export class InputCodeEditorView extends CodeEditorView { id: idx.toString(), range: baseRange.getInputRange(this.inputNumber), enabled: model.isUpToDate, - toggleState: derivedObservable('checkbox is checked', (reader) => { + toggleState: derived('checkbox is checked', (reader) => { const input = model .getState(baseRange) .read(reader) @@ -274,7 +273,7 @@ export class MergeConflictGutterItemView extends Disposable implements IGutterIt ) { super(); - this.item = new ObservableValue(item, 'item'); + this.item = observableValue('item', item); target.classList.add('merge-accept-gutter-marker'); @@ -309,7 +308,7 @@ export class MergeConflictGutterItemView extends Disposable implements IGutterIt checkBox.domNode.classList.add('accept-conflict-group'); this._register( - autorun((reader) => { + autorun('Update Checkbox', (reader) => { const item = this.item.read(reader)!; const value = item.toggleState.read(reader); const iconMap: Record = { @@ -326,7 +325,7 @@ export class MergeConflictGutterItemView extends Disposable implements IGutterIt } else { checkBox.enable(); } - }, 'Update Checkbox') + }) ); this._register(checkBox.onChange(() => { diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts index 0efc1d88247..e1557eb0c9a 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts @@ -4,17 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import { CompareResult } from 'vs/base/common/arrays'; +import { autorun, derived } from 'vs/base/common/observable'; import { IModelDeltaDecoration, MinimapPosition, OverviewRulerLane } from 'vs/editor/common/model'; import { localize } from 'vs/nls'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { autorun, derivedObservable } from 'vs/workbench/contrib/audioCues/browser/observable'; import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange'; import { applyObservableDecorations, join } from 'vs/workbench/contrib/mergeEditor/browser/utils'; import { handledConflictMinimapOverViewRulerColor, unhandledConflictMinimapOverViewRulerColor } from 'vs/workbench/contrib/mergeEditor/browser/view/colors'; import { CodeEditorView } from './codeEditorView'; export class ResultCodeEditorView extends CodeEditorView { - private readonly decorations = derivedObservable('result.decorations', reader => { + private readonly decorations = derived('result.decorations', reader => { const viewModel = this.viewModel.read(reader); if (!viewModel) { return []; @@ -107,7 +107,7 @@ export class ResultCodeEditorView extends CodeEditorView { this._register(applyObservableDecorations(this.editor, this.decorations)); - this._register(autorun(reader => { + this._register(autorun('update remainingConflicts label', reader => { const model = this.model.read(reader); if (!model) { return; @@ -126,6 +126,6 @@ export class ResultCodeEditorView extends CodeEditorView { count ); - }, 'update remainingConflicts label')); + })); } } diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts index 74629320cd2..544e07332cb 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts @@ -12,6 +12,7 @@ import { Color } from 'vs/base/common/color'; import { BugIndicatingError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; +import { autorunWithStore, IObservable } from 'vs/base/common/observable'; import { isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/mergeEditor'; @@ -37,7 +38,6 @@ import { AbstractTextEditor } from 'vs/workbench/browser/parts/editor/textEditor import { EditorInputWithOptions, EditorResourceAccessor, IEditorOpenContext } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { applyTextEditorOptions } from 'vs/workbench/common/editor/editorOptions'; -import { autorunWithStore, IObservable } from 'vs/workbench/contrib/audioCues/browser/observable'; import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; import { DocumentMapping, getOppositeDirection, MappingDirection } from 'vs/workbench/contrib/mergeEditor/browser/model/mapping'; import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel'; diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts index 46c954fb2c3..de910cceaee 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { findLast } from 'vs/base/common/arrays'; +import { derived, derivedObservableWithWritableCache, IReader, ITransaction, observableValue, transaction } from 'vs/base/common/observable'; import { ScrollType } from 'vs/editor/common/editorCommon'; -import { derivedObservable, derivedObservableWithWritableCache, IReader, ITransaction, ObservableValue, transaction } from 'vs/workbench/contrib/audioCues/browser/observable'; import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange'; import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel'; import { ModifiedBaseRange, ModifiedBaseRangeState } from 'vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange'; @@ -25,9 +25,9 @@ export class MergeEditorViewModel { return editors.find((e) => e.isFocused.read(reader)) || lastValue; }); - private readonly manuallySetActiveModifiedBaseRange = new ObservableValue< + private readonly manuallySetActiveModifiedBaseRange = observableValue< ModifiedBaseRange | undefined - >(undefined, 'manuallySetActiveModifiedBaseRange'); + >('manuallySetActiveModifiedBaseRange', undefined); private getRange(editor: CodeEditorView, modifiedBaseRange: ModifiedBaseRange, reader: IReader | undefined): LineRange { if (editor === this.resultCodeEditorView) { @@ -38,7 +38,7 @@ export class MergeEditorViewModel { } } - public readonly activeModifiedBaseRange = derivedObservable( + public readonly activeModifiedBaseRange = derived( 'activeModifiedBaseRange', (reader) => { const focusedEditor = this.lastFocusedEditor.read(reader); diff --git a/src/vs/workbench/contrib/mergeEditor/test/browser/model.test.ts b/src/vs/workbench/contrib/mergeEditor/test/browser/model.test.ts index 9963829694f..ea1a9794545 100644 --- a/src/vs/workbench/contrib/mergeEditor/test/browser/model.test.ts +++ b/src/vs/workbench/contrib/mergeEditor/test/browser/model.test.ts @@ -5,13 +5,13 @@ import assert = require('assert'); import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { transaction } from 'vs/base/common/observable'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Range } from 'vs/editor/common/core/range'; import { ITextModel } from 'vs/editor/common/model'; import { EditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker'; import { createModelServices, createTextModel } from 'vs/editor/test/common/testTextModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { transaction } from 'vs/workbench/contrib/audioCues/browser/observable'; import { EditorWorkerServiceDiffComputer } from 'vs/workbench/contrib/mergeEditor/browser/model/diffComputer'; import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel'; From 32e10d588247a94af33afe5ef91f5e0e27a91c05 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 4 Jul 2022 16:36:21 +0200 Subject: [PATCH 148/150] SCM - Fix inconsistent outline for the commit action button (#154091) Fix inconsistent outline for the commit action button --- src/vs/base/browser/ui/button/button.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/base/browser/ui/button/button.css b/src/vs/base/browser/ui/button/button.css index ed4131eefd3..5327fb0e1e3 100644 --- a/src/vs/base/browser/ui/button/button.css +++ b/src/vs/base/browser/ui/button/button.css @@ -38,6 +38,10 @@ cursor: pointer; } +.monaco-button-dropdown > .monaco-button:focus { + outline-offset: -1px !important; +} + .monaco-button-dropdown > .monaco-dropdown-button { margin-left: 1px; } From 3fc0a6b2d1f9e1eb9a94e1820c667d67cda11c3e Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 4 Jul 2022 16:36:43 +0200 Subject: [PATCH 149/150] SCM - Fix commit action button dropdown title (#154088) Fix commit action button dropdown title --- src/vs/base/browser/ui/button/button.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/base/browser/ui/button/button.ts b/src/vs/base/browser/ui/button/button.ts index a2349f9dc96..3187254843b 100644 --- a/src/vs/base/browser/ui/button/button.ts +++ b/src/vs/base/browser/ui/button/button.ts @@ -15,6 +15,7 @@ import { Emitter, Event as BaseEvent } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { mixin } from 'vs/base/common/objects'; +import { localize } from 'vs/nls'; import 'vs/css!./button'; export interface IButtonOptions extends IButtonStyles { @@ -263,6 +264,7 @@ export class ButtonWithDropdown extends Disposable implements IButton { this.action = this._register(new Action('primaryAction', this.button.label, undefined, true, async () => this._onDidClick.fire(undefined))); this.dropdownButton = this._register(new Button(this.element, { ...options, title: false, supportIcons: true })); + this.dropdownButton.element.title = localize("button dropdown more actions", 'More Actions...'); this.dropdownButton.element.classList.add('monaco-dropdown-button'); this.dropdownButton.icon = Codicon.dropDownButton; this._register(this.dropdownButton.onDidClick(e => { From 32406a71579a58a5f32cbbe593b3bd8d653bea17 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 4 Jul 2022 16:37:52 +0200 Subject: [PATCH 150/150] Debt - Prevent icon translations (#154094) Prevent icon translations --- extensions/git-base/src/remoteSource.ts | 2 +- extensions/git/src/actionButton.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/extensions/git-base/src/remoteSource.ts b/extensions/git-base/src/remoteSource.ts index 32b45b851f7..134af197316 100644 --- a/extensions/git-base/src/remoteSource.ts +++ b/extensions/git-base/src/remoteSource.ts @@ -70,7 +70,7 @@ class RemoteSourceProviderQuickPick { })); } } catch (err) { - this.quickpick!.items = [{ label: localize('error', "$(error) Error: {0}", err.message), alwaysShow: true }]; + this.quickpick!.items = [{ label: localize('error', "{0} Error: {1}", '$(error)', err.message), alwaysShow: true }]; console.error(err); } finally { this.quickpick!.busy = false; diff --git a/extensions/git/src/actionButton.ts b/extensions/git/src/actionButton.ts index 2eabbeaa9e6..7a6f295330b 100644 --- a/extensions/git/src/actionButton.ts +++ b/extensions/git/src/actionButton.ts @@ -82,21 +82,21 @@ export class ActionButtonCommand { switch (postCommitCommand) { case 'push': { - title = localize('scm button commit and push title', "$(arrow-up) Commit & Push"); + title = localize('scm button commit and push title', "{0} Commit & Push", '$(arrow-up)'); tooltip = this.state.isCommitInProgress ? localize('scm button committing pushing tooltip', "Committing & Pushing Changes...") : localize('scm button commit push tooltip', "Commit & Push Changes"); break; } case 'sync': { - title = localize('scm button commit and sync title', "$(sync) Commit & Sync"); + title = localize('scm button commit and sync title', "{0} Commit & Sync", '$(sync)'); tooltip = this.state.isCommitInProgress ? localize('scm button committing synching tooltip', "Committing & Synching Changes...") : localize('scm button commit sync tooltip', "Commit & Sync Changes"); break; } default: { - title = localize('scm button commit title', "$(check) Commit"); + title = localize('scm button commit title', "{0} Commit", '$(check)'); tooltip = this.state.isCommitInProgress ? localize('scm button committing tooltip', "Committing Changes...") : localize('scm button commit tooltip', "Commit Changes");