From f1a2ed170977cf869265957c7ee078fcb3f30588 Mon Sep 17 00:00:00 2001 From: justanotheranonymoususer Date: Thu, 23 Sep 2021 20:13:17 +0300 Subject: [PATCH 01/24] Option to preserve focus while showing view --- .../browser/parts/views/viewsService.ts | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/browser/parts/views/viewsService.ts b/src/vs/workbench/browser/parts/views/viewsService.ts index aeb883be2e1..1d690f2a90b 100644 --- a/src/vs/workbench/browser/parts/views/viewsService.ts +++ b/src/vs/workbench/browser/parts/views/viewsService.ts @@ -457,9 +457,10 @@ export class ViewsService extends Disposable implements IViewsService { private registerFocusViewAction(viewDescriptor: IViewDescriptor, category?: string | ILocalizedString): IDisposable { return registerAction2(class FocusViewAction extends Action2 { constructor() { + const title = localize({ key: 'focus view', comment: ['{0} indicates the name of the view to be focused.'] }, "Focus on {0} View", viewDescriptor.name); super({ id: viewDescriptor.focusCommand ? viewDescriptor.focusCommand.id : `${viewDescriptor.id}.focus`, - title: { original: `Focus on ${viewDescriptor.name} View`, value: localize({ key: 'focus view', comment: ['{0} indicates the name of the view to be focused.'] }, "Focus on {0} View", viewDescriptor.name) }, + title: { original: `Focus on ${viewDescriptor.name} View`, value: title }, category, menu: [{ id: MenuId.CommandPalette, @@ -473,11 +474,29 @@ export class ViewsService extends Disposable implements IViewsService { linux: viewDescriptor.focusCommand?.keybindings?.linux, mac: viewDescriptor.focusCommand?.keybindings?.mac, win: viewDescriptor.focusCommand?.keybindings?.win + }, + description: { + description: title, + args: [ + { + name: 'focusOptions', + description: 'Focus Options', + schema: { + type: 'object', + properties: { + 'preserveFocus': { + type: 'boolean', + default: false + } + }, + } + } + ] } }); } - run(accessor: ServicesAccessor): void { - accessor.get(IViewsService).openView(viewDescriptor.id, true); + run(accessor: ServicesAccessor, showOptions?: { preserveFocus?: boolean }): void { + accessor.get(IViewsService).openView(viewDescriptor.id, !preserveFocus); } }); } From e922713ecf8ce7a0b6895f699c8c9120000e9673 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 25 Oct 2021 09:42:57 +0200 Subject: [PATCH 02/24] Fixes #135726 by properly skipping unbalanced bracket pairs when rendering bracket pair guides. --- src/vs/editor/common/model/textModel.ts | 41 ++++++++++++++++--------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 5e3549179d8..410cc180de6 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -3098,9 +3098,7 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati endLineNumber, this.getLineMaxColumn(endLineNumber) ) - ) - // Exclude bracket pairs that are not balanced. - .filter(b => b.closingBracketRange !== undefined); + ); let activeBracketPairRange: Range | undefined = undefined; if (activePosition && bracketPairs.length > 0) { @@ -3136,7 +3134,7 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati visibleEndColumn: number, bracketPair: BracketPairInfo, renderHorizontalEndLineAtTheBottom: boolean - }>(); + } | null>(); const nextGuides = new Array(); const colorProvider = new BracketPairGuidesClassNames(); @@ -3169,19 +3167,30 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati // TODO: Consider indentation when computing guideVisibleColumn const start = pair.openingBracketRange.getStartPosition(); const end = (pair.closingBracketRange?.getStartPosition() ?? pair.range.getEndPosition()); - activeGuides[pair.nestingLevel] = { - nestingLevel: pair.nestingLevel, - guideVisibleColumn, - start, - visibleStartColumn: this.getVisibleColumnFromPosition(start), - end, - visibleEndColumn: this.getVisibleColumnFromPosition(end), - bracketPair: pair, - renderHorizontalEndLineAtTheBottom - }; + + + if (pair.closingBracketRange === undefined) { + // Don't show guides for bracket pairs that are not balanced. + // See #135125. + activeGuides[pair.nestingLevel] = null; + } else { + activeGuides[pair.nestingLevel] = { + nestingLevel: pair.nestingLevel, + guideVisibleColumn, + start, + visibleStartColumn: this.getVisibleColumnFromPosition(start), + end, + visibleEndColumn: this.getVisibleColumnFromPosition(end), + bracketPair: pair, + renderHorizontalEndLineAtTheBottom + }; + } } for (const line of activeGuides) { + if (!line) { + continue; + } const isActive = activeBracketPairRange && line.bracketPair.range.equalsRange(activeBracketPairRange); const className = @@ -3214,7 +3223,9 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati // Going backwards, so the last guide potentially replaces others for (let i = activeGuides.length - 1; i >= 0; i--) { const line = activeGuides[i]; - + if (!line) { + continue; + } const isActive = options.highlightActive && activeBracketPairRange && line.bracketPair.range.equalsRange(activeBracketPairRange); From ad36148dd2d6ea51f06496438691000fb5fdbabf Mon Sep 17 00:00:00 2001 From: John Murray Date: Mon, 25 Oct 2021 08:48:23 +0100 Subject: [PATCH 03/24] correct doc for `count` property of `SourceControl` (#117765) (#132242) --- src/vs/vscode.d.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index edd273928b2..77abc5089bd 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -12734,8 +12734,9 @@ declare module 'vscode' { * The UI-visible count of {@link SourceControlResourceState resource states} of * this source control. * - * Equals to the total number of {@link SourceControlResourceState resource state} - * of this source control, if undefined. + * If undefined, this source control will + * - display its UI-visible count as zero, and + * - contribute the count of its {@link SourceControlResourceState resource states} to the UI-visible aggregated count for all source controls */ count?: number; From ba5517b91151ab40611bfb6d88d0b9f9c4472954 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 25 Oct 2021 09:52:59 +0200 Subject: [PATCH 04/24] Fixes #135151 by not colorizing brackets or rendering line scopes in xml. --- extensions/xml/xml.language-configuration.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extensions/xml/xml.language-configuration.json b/extensions/xml/xml.language-configuration.json index 22842960497..d04db08380c 100644 --- a/extensions/xml/xml.language-configuration.json +++ b/extensions/xml/xml.language-configuration.json @@ -25,6 +25,8 @@ { "open": "(", "close": ")" }, { "open": "<", "close": ">" } ], + "colorizedBracketPairs": [ + ], "folding": { "markers": { "start": "^\\s*", From e7647291206dbe540c0d5121889b4f9f9c9d43f7 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 25 Oct 2021 10:24:05 +0200 Subject: [PATCH 05/24] prevent unneeded re-render of breadcrumbs items --- src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index a5c9bbe69d4..fcfea9b352d 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -62,7 +62,7 @@ class OutlineItem extends BreadcrumbsItem { if (!(other instanceof OutlineItem)) { return false; } - return this.element === other.element && + return this.element.element === other.element.element && this.options.showFileIcons === other.options.showFileIcons && this.options.showSymbolIcons === other.options.showSymbolIcons; } From 803d2b5db9fb42e6f4a92679d4479309a9ef5b46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Mon, 25 Oct 2021 10:38:47 +0200 Subject: [PATCH 06/24] fix telemetry event --- .../services/extensions/browser/extensionUrlHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts index a44cc8bf3dc..6dd20722202 100644 --- a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts +++ b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts @@ -261,7 +261,7 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { // Extension is disabled. Enable the extension and reload the window to handle. else if (this.extensionEnablementService.canChangeEnablement(extension)) { - this.telemetryService.publicLog2('uri_invoked/enable_extension/accept', { extensionId: extensionIdentifier.id }); + this.telemetryService.publicLog2('uri_invoked/enable_extension/start', { extensionId: extensionIdentifier.id }); const result = await this.dialogService.confirm({ message: localize('enableAndHandle', "Extension '{0}' is disabled. Would you like to enable the extension and reload the window to open the URL?", extension.manifest.displayName || extension.manifest.name), detail: `${extension.manifest.displayName || extension.manifest.name} (${extensionIdentifier.id}) wants to open a URL:\n\n${uri.toString()}`, From e37c9966e9248a27e8df496aa8c1cb06f5c90d0c Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 25 Oct 2021 10:42:54 +0200 Subject: [PATCH 07/24] Add vscode-livereview repo --- .vscode/notebooks/my-work.github-issues | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/notebooks/my-work.github-issues b/.vscode/notebooks/my-work.github-issues index 6e2a7acfd77..1f66b716852 100644 --- a/.vscode/notebooks/my-work.github-issues +++ b/.vscode/notebooks/my-work.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "// list of repos we work in\n$repos=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-references-view repo:microsoft/vscode-anycode repo:microsoft/vscode-hexeditor repo:microsoft/vscode-extension-telemetry\n\n// current milestone name\n$milestone=milestone:\"October 2021\"" + "value": "// list of repos we work in\n$repos=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-references-view repo:microsoft/vscode-anycode repo:microsoft/vscode-hexeditor repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-livepreview\n\n// current milestone name\n$milestone=milestone:\"October 2021\"" }, { "kind": 1, From 4f9022bee325cecfa646546de6972ecb1e943ed9 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 25 Oct 2021 10:47:43 +0200 Subject: [PATCH 08/24] fix #88927 --- .../extensions/browser/extensionEditor.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 2d393097c0f..39a6a25f2c0 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -1107,11 +1107,18 @@ export class ExtensionEditor extends EditorPane { $('th', undefined, localize('description', "Description")), $('th', undefined, localize('default', "Default")) ), - ...contrib.map(key => $('tr', undefined, - $('td', undefined, $('code', undefined, key)), - $('td', undefined, properties[key].description || (properties[key].markdownDescription && renderMarkdown({ value: properties[key].markdownDescription }, { actionHandler: { callback: (content) => this.openerService.open(content).catch(onUnexpectedError), disposables: this.contentDisposables } }))), - $('td', undefined, $('code', undefined, `${isUndefined(properties[key].default) ? getDefaultValue(properties[key].type) : properties[key].default}`)) - )) + ...contrib.map(key => { + let description: (Node | string) = properties[key].description; + if (properties[key].markdownDescription) { + const { element, dispose } = renderMarkdown({ value: properties[key].markdownDescription }, { actionHandler: { callback: (content) => this.openerService.open(content).catch(onUnexpectedError), disposables: this.contentDisposables } }); + description = element; + this.contentDisposables.add(toDisposable(dispose)); + } + return $('tr', undefined, + $('td', undefined, $('code', undefined, key)), + $('td', undefined, description), + $('td', undefined, $('code', undefined, `${isUndefined(properties[key].default) ? getDefaultValue(properties[key].type) : properties[key].default}`))); + }) ) ); From 96c0bfba4cc76dff725dd5189d8606e858e3cf41 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 25 Oct 2021 10:52:34 +0200 Subject: [PATCH 09/24] fix compilation --- src/vs/workbench/browser/parts/views/viewsService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/views/viewsService.ts b/src/vs/workbench/browser/parts/views/viewsService.ts index 1d690f2a90b..e782029440e 100644 --- a/src/vs/workbench/browser/parts/views/viewsService.ts +++ b/src/vs/workbench/browser/parts/views/viewsService.ts @@ -495,8 +495,8 @@ export class ViewsService extends Disposable implements IViewsService { } }); } - run(accessor: ServicesAccessor, showOptions?: { preserveFocus?: boolean }): void { - accessor.get(IViewsService).openView(viewDescriptor.id, !preserveFocus); + run(accessor: ServicesAccessor, options?: { preserveFocus?: boolean }): void { + accessor.get(IViewsService).openView(viewDescriptor.id, !options?.preserveFocus); } }); } From 208e84ae1e6742305d7f3677273f2a39ee2842d3 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 25 Oct 2021 10:55:33 +0200 Subject: [PATCH 10/24] Implements #134801 by introducing editorBracketPairGuideBackground{1..6} and editorBracketPairGuideActiveBackground{1..6}. --- .../viewParts/indentGuides/indentGuides.ts | 64 ++++++++++++++----- .../editor/common/view/editorColorRegistry.ts | 15 +++++ 2 files changed, 62 insertions(+), 17 deletions(-) diff --git a/src/vs/editor/browser/viewParts/indentGuides/indentGuides.ts b/src/vs/editor/browser/viewParts/indentGuides/indentGuides.ts index acb389724ac..ebc9516ff5a 100644 --- a/src/vs/editor/browser/viewParts/indentGuides/indentGuides.ts +++ b/src/vs/editor/browser/viewParts/indentGuides/indentGuides.ts @@ -5,7 +5,7 @@ import 'vs/css!./indentGuides'; import { DynamicViewOverlay } from 'vs/editor/browser/view/dynamicViewOverlay'; -import { editorActiveIndentGuides, editorBracketHighlightingForeground1, editorBracketHighlightingForeground2, editorBracketHighlightingForeground3, editorBracketHighlightingForeground4, editorBracketHighlightingForeground5, editorBracketHighlightingForeground6, editorIndentGuides } from 'vs/editor/common/view/editorColorRegistry'; +import { editorActiveIndentGuides, editorBracketHighlightingForeground1, editorBracketHighlightingForeground2, editorBracketHighlightingForeground3, editorBracketHighlightingForeground4, editorBracketHighlightingForeground5, editorBracketHighlightingForeground6, editorBracketPairGuideActiveBackground1, editorBracketPairGuideActiveBackground2, editorBracketPairGuideActiveBackground3, editorBracketPairGuideActiveBackground4, editorBracketPairGuideActiveBackground5, editorBracketPairGuideActiveBackground6, editorBracketPairGuideBackground1, editorBracketPairGuideBackground2, editorBracketPairGuideBackground3, editorBracketPairGuideBackground4, editorBracketPairGuideBackground5, editorBracketPairGuideBackground6, editorIndentGuides } from 'vs/editor/common/view/editorColorRegistry'; import { RenderingContext } from 'vs/editor/common/view/renderingContext'; import { ViewContext } from 'vs/editor/common/view/viewContext'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; @@ -16,6 +16,7 @@ import { HorizontalGuidesState, IndentGuide } from 'vs/editor/common/model'; import { ArrayQueue } from 'vs/base/common/arrays'; import { BracketPairGuidesClassNames } from 'vs/editor/common/model/textModel'; import { Color } from 'vs/base/common/color'; +import { isDefined } from 'vs/base/common/types'; export class IndentGuidesOverlay extends DynamicViewOverlay { @@ -241,6 +242,13 @@ export class IndentGuidesOverlay extends DynamicViewOverlay { } } +function transparentToUndefined(color: Color | undefined): Color | undefined { + if (color && color.isTransparent()) { + return undefined; + } + return color; +} + registerThemingParticipant((theme, collector) => { const editorIndentGuidesColor = theme.getColor(editorIndentGuides); if (editorIndentGuidesColor) { @@ -252,26 +260,48 @@ registerThemingParticipant((theme, collector) => { } const colors = [ - editorBracketHighlightingForeground1, - editorBracketHighlightingForeground2, - editorBracketHighlightingForeground3, - editorBracketHighlightingForeground4, - editorBracketHighlightingForeground5, - editorBracketHighlightingForeground6 + { bracketColor: editorBracketHighlightingForeground1, guideColor: editorBracketPairGuideBackground1, guideColorActive: editorBracketPairGuideActiveBackground1 }, + { bracketColor: editorBracketHighlightingForeground2, guideColor: editorBracketPairGuideBackground2, guideColorActive: editorBracketPairGuideActiveBackground2 }, + { bracketColor: editorBracketHighlightingForeground3, guideColor: editorBracketPairGuideBackground3, guideColorActive: editorBracketPairGuideActiveBackground3 }, + { bracketColor: editorBracketHighlightingForeground4, guideColor: editorBracketPairGuideBackground4, guideColorActive: editorBracketPairGuideActiveBackground4 }, + { bracketColor: editorBracketHighlightingForeground5, guideColor: editorBracketPairGuideBackground5, guideColorActive: editorBracketPairGuideActiveBackground5 }, + { bracketColor: editorBracketHighlightingForeground6, guideColor: editorBracketPairGuideBackground6, guideColorActive: editorBracketPairGuideActiveBackground6 } ]; const colorProvider = new BracketPairGuidesClassNames(); + let colorValues = colors - .map(c => theme.getColor(c)) - .filter((c): c is Color => !!c) - .filter(c => !c.isTransparent()); + .map(c => { + const bracketColor = theme.getColor(c.bracketColor); + const guideColor = theme.getColor(c.guideColor); + const guideColorActive = theme.getColor(c.guideColorActive); - for (let level = 0; level < 30; level++) { - const color = colorValues[level % colorValues.length]; - collector.addRule(`.monaco-editor .${colorProvider.getInlineClassNameOfLevel(level).replace(/ /g, '.')}.vertical { opacity: 0.3; box-shadow: 1px 0 0 0 ${color} inset; }`); - collector.addRule(`.monaco-editor .${colorProvider.getInlineClassNameOfLevel(level).replace(/ /g, '.')}.horizontal-top { opacity: 0.3; border-top: 1px solid ${color}; }`); - collector.addRule(`.monaco-editor .${colorProvider.getInlineClassNameOfLevel(level).replace(/ /g, '.')}.horizontal-bottom { opacity: 0.3; border-bottom: 1px solid ${color}; }`); + const effectiveGuideColor = transparentToUndefined(transparentToUndefined(guideColor) ?? bracketColor?.transparent(0.3)); + const effectiveGuideColorActive = transparentToUndefined(transparentToUndefined(guideColorActive) ?? bracketColor); + + if (!effectiveGuideColor || !effectiveGuideColorActive) { + return undefined; + } + + return { + guideColor: effectiveGuideColor, + guideColorActive: effectiveGuideColorActive, + }; + }) + .filter(isDefined); + + if (colorValues.length > 0) { + for (let level = 0; level < 30; level++) { + const colors = colorValues[level % colorValues.length]; + collector.addRule(`.monaco-editor .${colorProvider.getInlineClassNameOfLevel(level).replace(/ /g, '.')} { --guide-color: ${colors.guideColor}; --guide-color-active: ${colors.guideColorActive}; }`); + } + + collector.addRule(`.monaco-editor .vertical { box-shadow: 1px 0 0 0 var(--guide-color) inset; }`); + collector.addRule(`.monaco-editor .horizontal-top { border-top: 1px solid var(--guide-color); }`); + collector.addRule(`.monaco-editor .horizontal-bottom { border-bottom: 1px solid var(--guide-color); }`); + + collector.addRule(`.monaco-editor .vertical.${colorProvider.activeClassName} { box-shadow: 1px 0 0 0 var(--guide-color-active) inset; }`); + collector.addRule(`.monaco-editor .horizontal-top.${colorProvider.activeClassName} { border-top: 1px solid var(--guide-color-active); }`); + collector.addRule(`.monaco-editor .horizontal-bottom.${colorProvider.activeClassName} { border-bottom: 1px solid var(--guide-color-active); }`); } - - collector.addRule(`.monaco-editor .${colorProvider.activeClassName} { opacity: 1 !important; }`); }); diff --git a/src/vs/editor/common/view/editorColorRegistry.ts b/src/vs/editor/common/view/editorColorRegistry.ts index 808a422a1e3..d0950a54331 100644 --- a/src/vs/editor/common/view/editorColorRegistry.ts +++ b/src/vs/editor/common/view/editorColorRegistry.ts @@ -61,6 +61,21 @@ export const editorBracketHighlightingForeground6 = registerColor('editorBracket export const editorBracketHighlightingUnexpectedBracketForeground = registerColor('editorBracketHighlight.unexpectedBracket.foreground', { dark: new Color(new RGBA(255, 18, 18, 0.8)), light: new Color(new RGBA(255, 18, 18, 0.8)), hc: new Color(new RGBA(255, 50, 50, 1)) }, nls.localize('editorBracketHighlightUnexpectedBracketForeground', 'Foreground color of unexpected brackets.')); +export const editorBracketPairGuideBackground1 = registerColor('editorBracketPairGuide.background1', { dark: '#00000000', light: '#00000000', hc: '#00000000' }, nls.localize('editorBracketPairGuide.background1', 'Background color of inactive bracket pair guides (1). Requires enabling bracket pair guides.')); +export const editorBracketPairGuideBackground2 = registerColor('editorBracketPairGuide.background2', { dark: '#00000000', light: '#00000000', hc: '#00000000' }, nls.localize('editorBracketPairGuide.background2', 'Background color of inactive bracket pair guides (2). Requires enabling bracket pair guides.')); +export const editorBracketPairGuideBackground3 = registerColor('editorBracketPairGuide.background3', { dark: '#00000000', light: '#00000000', hc: '#00000000' }, nls.localize('editorBracketPairGuide.background3', 'Background color of inactive bracket pair guides (3). Requires enabling bracket pair guides.')); +export const editorBracketPairGuideBackground4 = registerColor('editorBracketPairGuide.background4', { dark: '#00000000', light: '#00000000', hc: '#00000000' }, nls.localize('editorBracketPairGuide.background4', 'Background color of inactive bracket pair guides (4). Requires enabling bracket pair guides.')); +export const editorBracketPairGuideBackground5 = registerColor('editorBracketPairGuide.background5', { dark: '#00000000', light: '#00000000', hc: '#00000000' }, nls.localize('editorBracketPairGuide.background5', 'Background color of inactive bracket pair guides (5). Requires enabling bracket pair guides.')); +export const editorBracketPairGuideBackground6 = registerColor('editorBracketPairGuide.background6', { dark: '#00000000', light: '#00000000', hc: '#00000000' }, nls.localize('editorBracketPairGuide.background6', 'Background color of inactive bracket pair guides (6). Requires enabling bracket pair guides.')); + +export const editorBracketPairGuideActiveBackground1 = registerColor('editorBracketPairGuide.activeBackground1', { dark: '#00000000', light: '#00000000', hc: '#00000000' }, nls.localize('editorBracketPairGuide.activeBackground1', 'Background color of active bracket pair guides (1). Requires enabling bracket pair guides.')); +export const editorBracketPairGuideActiveBackground2 = registerColor('editorBracketPairGuide.activeBackground2', { dark: '#00000000', light: '#00000000', hc: '#00000000' }, nls.localize('editorBracketPairGuide.activeBackground2', 'Background color of active bracket pair guides (2). Requires enabling bracket pair guides.')); +export const editorBracketPairGuideActiveBackground3 = registerColor('editorBracketPairGuide.activeBackground3', { dark: '#00000000', light: '#00000000', hc: '#00000000' }, nls.localize('editorBracketPairGuide.activeBackground3', 'Background color of active bracket pair guides (3). Requires enabling bracket pair guides.')); +export const editorBracketPairGuideActiveBackground4 = registerColor('editorBracketPairGuide.activeBackground4', { dark: '#00000000', light: '#00000000', hc: '#00000000' }, nls.localize('editorBracketPairGuide.activeBackground4', 'Background color of active bracket pair guides (4). Requires enabling bracket pair guides.')); +export const editorBracketPairGuideActiveBackground5 = registerColor('editorBracketPairGuide.activeBackground5', { dark: '#00000000', light: '#00000000', hc: '#00000000' }, nls.localize('editorBracketPairGuide.activeBackground5', 'Background color of active bracket pair guides (5). Requires enabling bracket pair guides.')); +export const editorBracketPairGuideActiveBackground6 = registerColor('editorBracketPairGuide.activeBackground6', { dark: '#00000000', light: '#00000000', hc: '#00000000' }, nls.localize('editorBracketPairGuide.activeBackground6', 'Background color of active bracket pair guides (6). Requires enabling bracket pair guides.')); + + // contains all color rules that used to defined in editor/browser/widget/editor.css registerThemingParticipant((theme, collector) => { const background = theme.getColor(editorBackground); From 1492ce2ad07deafe3a3b5f07e078a7ec39388f88 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 25 Oct 2021 11:18:27 +0200 Subject: [PATCH 11/24] make ResourceMap case preserving, even better fix for #128198, fyi @bpasero --- src/vs/base/common/map.ts | 32 +++++++++++-------- src/vs/base/test/common/map.test.ts | 19 +++++++++++ .../api/common/extHostDiagnostics.ts | 5 ++- 3 files changed, 40 insertions(+), 16 deletions(-) diff --git a/src/vs/base/common/map.ts b/src/vs/base/common/map.ts index 132b8f79e65..b45efcf3784 100644 --- a/src/vs/base/common/map.ts +++ b/src/vs/base/common/map.ts @@ -741,13 +741,17 @@ interface ResourceMapKeyFn { (resource: URI): string; } +class ResourceMapEntry { + constructor(readonly uri: URI, readonly value: T) { } +} + export class ResourceMap implements Map { private static readonly defaultToKey = (resource: URI) => resource.toString(); readonly [Symbol.toStringTag] = 'ResourceMap'; - private readonly map: Map; + private readonly map: Map>; private readonly toKey: ResourceMapKeyFn; /** @@ -774,12 +778,12 @@ export class ResourceMap implements Map { } set(resource: URI, value: T): this { - this.map.set(this.toKey(resource), value); + this.map.set(this.toKey(resource), new ResourceMapEntry(resource, value)); return this; } get(resource: URI): T | undefined { - return this.map.get(this.toKey(resource)); + return this.map.get(this.toKey(resource))?.value; } has(resource: URI): boolean { @@ -802,30 +806,32 @@ export class ResourceMap implements Map { if (typeof thisArg !== 'undefined') { clb = clb.bind(thisArg); } - for (let [index, value] of this.map) { - clb(value, URI.parse(index), this); + for (let [_, entry] of this.map) { + clb(entry.value, entry.uri, this); } } - values(): IterableIterator { - return this.map.values(); + *values(): IterableIterator { + for (let entry of this.map.values()) { + yield entry.value; + } } *keys(): IterableIterator { - for (let key of this.map.keys()) { - yield URI.parse(key); + for (let entry of this.map.values()) { + yield entry.uri; } } *entries(): IterableIterator<[URI, T]> { - for (let tuple of this.map.entries()) { - yield [URI.parse(tuple[0]), tuple[1]]; + for (let entry of this.map.values()) { + yield [entry.uri, entry.value]; } } *[Symbol.iterator](): IterableIterator<[URI, T]> { - for (let item of this.map) { - yield [URI.parse(item[0]), item[1]]; + for (let [, entry] of this.map) { + yield [entry.uri, entry.value]; } } } diff --git a/src/vs/base/test/common/map.test.ts b/src/vs/base/test/common/map.test.ts index fc0f1fc0034..633749fba96 100644 --- a/src/vs/base/test/common/map.test.ts +++ b/src/vs/base/test/common/map.test.ts @@ -1189,6 +1189,25 @@ suite('Map', () => { assert.strictEqual(map.get(windowsFile), 'true'); assert.strictEqual(map.get(uncFile), 'true'); }); + + test('ResourceMap - files (ignorecase, BUT preservecase)', function () { + const map = new ResourceMap(uri => extUriIgnorePathCase.getComparisonKey(uri)); + + const fileA = URI.parse('file://some/filea'); + const fileAUpper = URI.parse('file://SOME/FILEA'); + + map.set(fileA, 1); + assert.strictEqual(map.get(fileA), 1); + assert.strictEqual(map.get(fileAUpper), 1); + assert.deepStrictEqual(Array.from(map.keys()).map(String), [fileA].map(String)); + assert.deepStrictEqual(Array.from(map), [[fileA, 1]]); + + map.set(fileAUpper, 1); + assert.strictEqual(map.get(fileA), 1); + assert.strictEqual(map.get(fileAUpper), 1); + assert.deepStrictEqual(Array.from(map.keys()).map(String), [fileAUpper].map(String)); + assert.deepStrictEqual(Array.from(map), [[fileAUpper, 1]]); + }); }); diff --git a/src/vs/workbench/api/common/extHostDiagnostics.ts b/src/vs/workbench/api/common/extHostDiagnostics.ts index 00038bd692d..bacfe4bdd80 100644 --- a/src/vs/workbench/api/common/extHostDiagnostics.ts +++ b/src/vs/workbench/api/common/extHostDiagnostics.ts @@ -16,13 +16,12 @@ import { ResourceMap } from 'vs/base/common/map'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo'; import { IExtUri } from 'vs/base/common/resources'; -import { SkipList } from 'vs/base/common/skipList'; export class DiagnosticCollection implements vscode.DiagnosticCollection { readonly #proxy: MainThreadDiagnosticsShape | undefined; readonly #onDidChangeDiagnostics: Emitter; - readonly #data: SkipList; + readonly #data: ResourceMap; private _isDisposed = false; @@ -34,7 +33,7 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection { proxy: MainThreadDiagnosticsShape | undefined, onDidChangeDiagnostics: Emitter ) { - this.#data = new SkipList((a, b) => extUri.compare(a, b)); + this.#data = new ResourceMap(uri => extUri.getComparisonKey(uri)); this.#proxy = proxy; this.#onDidChangeDiagnostics = onDidChangeDiagnostics; } From c78245a79c4a8f080ac98f62c099fcc54da9a98d Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 25 Oct 2021 11:33:47 +0200 Subject: [PATCH 12/24] Fixes #135529 by not rendering guides on top of others. --- src/vs/editor/common/model/textModel.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 410cc180de6..51b7208cdb8 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -3244,7 +3244,8 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati continue; } - if (line.guideVisibleColumn > lastVisibleColumnCount) { + if (line.guideVisibleColumn >= lastVisibleColumnCount && !isActive) { + // Don't render a guide on top of an existing guide, unless it is active. continue; } lastVisibleColumnCount = line.guideVisibleColumn; From 3caab42e01ea478c24811796d90191e1296a5fa1 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 25 Oct 2021 11:53:26 +0200 Subject: [PATCH 13/24] refine check for knowing if a view zone is visible, https://github.com/microsoft/vscode/issues/121516 --- src/vs/editor/contrib/codelens/codelensWidget.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/codelens/codelensWidget.ts b/src/vs/editor/contrib/codelens/codelensWidget.ts index f1c2e6ff2c2..938a5c1e5d9 100644 --- a/src/vs/editor/contrib/codelens/codelensWidget.ts +++ b/src/vs/editor/contrib/codelens/codelensWidget.ts @@ -44,6 +44,11 @@ class CodeLensViewZone implements IViewZone { this._onHeight(); } } + + isVisible(): boolean { + return this._lastHeight !== 0 + && this.domNode.hasAttribute('monaco-visible-view-zone'); + } } class CodeLensContentWidget implements IContentWidget { @@ -289,7 +294,7 @@ export class CodeLensWidget { } computeIfNecessary(model: ITextModel): CodeLensItem[] | null { - if (!this._viewZone.domNode.hasAttribute('monaco-visible-view-zone')) { + if (!this._viewZone.isVisible()) { return null; } From bbf53eda82dc92f7a7395788833d820cd48320f3 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 25 Oct 2021 11:55:38 +0200 Subject: [PATCH 14/24] storage - use fake timers for clear test (#135075) --- .../test/browser/storageService.test.ts | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/vs/platform/storage/test/browser/storageService.test.ts b/src/vs/platform/storage/test/browser/storageService.test.ts index 3f08fdc934a..5c4665ad1d4 100644 --- a/src/vs/platform/storage/test/browser/storageService.test.ts +++ b/src/vs/platform/storage/test/browser/storageService.test.ts @@ -8,6 +8,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { Storage } from 'vs/base/parts/storage/common/storage'; import { flakySuite } from 'vs/base/test/common/testUtils'; +import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; import { FileService } from 'vs/platform/files/common/fileService'; import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; import { NullLogService } from 'vs/platform/log/common/log'; @@ -67,19 +68,21 @@ flakySuite('StorageService (browser specific)', () => { }); test('clear', async () => { - storageService.store('bar', 'foo', StorageScope.GLOBAL, StorageTarget.MACHINE); - storageService.store('bar', 3, StorageScope.GLOBAL, StorageTarget.USER); - storageService.store('bar', 'foo', StorageScope.WORKSPACE, StorageTarget.MACHINE); - storageService.store('bar', 3, StorageScope.WORKSPACE, StorageTarget.USER); + await runWithFakedTimers({ useFakeTimers: true }, async () => { + storageService.store('bar', 'foo', StorageScope.GLOBAL, StorageTarget.MACHINE); + storageService.store('bar', 3, StorageScope.GLOBAL, StorageTarget.USER); + storageService.store('bar', 'foo', StorageScope.WORKSPACE, StorageTarget.MACHINE); + storageService.store('bar', 3, StorageScope.WORKSPACE, StorageTarget.USER); - await storageService.clear(); + await storageService.clear(); - for (const scope of [StorageScope.GLOBAL, StorageScope.WORKSPACE]) { - for (const target of [StorageTarget.USER, StorageTarget.MACHINE]) { - strictEqual(storageService.get('bar', scope), undefined); - strictEqual(storageService.keys(scope, target).length, 0); + for (const scope of [StorageScope.GLOBAL, StorageScope.WORKSPACE]) { + for (const target of [StorageTarget.USER, StorageTarget.MACHINE]) { + strictEqual(storageService.get('bar', scope), undefined); + strictEqual(storageService.keys(scope, target).length, 0); + } } - } + }); }); }); From d93c0197854af32a272351b0e41ef95417663ff2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Mon, 25 Oct 2021 11:59:19 +0200 Subject: [PATCH 15/24] fixes #123165 --- src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index a0634775d9d..d7dd132c96b 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -292,7 +292,8 @@ class DirtyDiffWidget extends PeekViewWidget { fixedOverflowWidgets: true, minimap: { enabled: false }, renderSideBySide: false, - readOnly: false + readOnly: false, + renderIndicators: false }; this.diffEditor = this.instantiationService.createInstance(EmbeddedDiffEditorWidget, container, options, this.editor); From 1c0447f9c3d8a1b3a521f64d454a34ef813b168a Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 25 Oct 2021 12:01:15 +0200 Subject: [PATCH 16/24] backups - use fake timers in some slow tests (#135075) --- .../electron-main/backupMainService.test.ts | 157 +++++++++--------- 1 file changed, 81 insertions(+), 76 deletions(-) diff --git a/src/vs/platform/backup/test/electron-main/backupMainService.test.ts b/src/vs/platform/backup/test/electron-main/backupMainService.test.ts index 9c9e8e4c712..f4079d07649 100644 --- a/src/vs/platform/backup/test/electron-main/backupMainService.test.ts +++ b/src/vs/platform/backup/test/electron-main/backupMainService.test.ts @@ -13,6 +13,7 @@ import * as platform from 'vs/base/common/platform'; import { isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import * as pfs from 'vs/base/node/pfs'; +import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils'; import { IWorkspaceBackupInfo } from 'vs/platform/backup/electron-main/backup'; import { BackupMainService } from 'vs/platform/backup/electron-main/backupMainService'; @@ -136,93 +137,97 @@ flakySuite('BackupMainService', () => { }); test('service validates backup workspaces on startup and cleans up (folder workspaces)', async function () { + runWithFakedTimers({}, async () => { - // 1) backup workspace path does not exist - service.registerFolderBackupSync(fooFile); - service.registerFolderBackupSync(barFile); - await service.initialize(); - assertEqualUris(service.getFolderBackupPaths(), []); + // 1) backup workspace path does not exist + service.registerFolderBackupSync(fooFile); + service.registerFolderBackupSync(barFile); + await service.initialize(); + assertEqualUris(service.getFolderBackupPaths(), []); - // 2) backup workspace path exists with empty contents within - fs.mkdirSync(service.toBackupPath(fooFile)); - fs.mkdirSync(service.toBackupPath(barFile)); - service.registerFolderBackupSync(fooFile); - service.registerFolderBackupSync(barFile); - await service.initialize(); - assertEqualUris(service.getFolderBackupPaths(), []); - assert.ok(!fs.existsSync(service.toBackupPath(fooFile))); - assert.ok(!fs.existsSync(service.toBackupPath(barFile))); + // 2) backup workspace path exists with empty contents within + fs.mkdirSync(service.toBackupPath(fooFile)); + fs.mkdirSync(service.toBackupPath(barFile)); + service.registerFolderBackupSync(fooFile); + service.registerFolderBackupSync(barFile); + await service.initialize(); + assertEqualUris(service.getFolderBackupPaths(), []); + assert.ok(!fs.existsSync(service.toBackupPath(fooFile))); + assert.ok(!fs.existsSync(service.toBackupPath(barFile))); - // 3) backup workspace path exists with empty folders within - fs.mkdirSync(service.toBackupPath(fooFile)); - fs.mkdirSync(service.toBackupPath(barFile)); - fs.mkdirSync(path.join(service.toBackupPath(fooFile), Schemas.file)); - fs.mkdirSync(path.join(service.toBackupPath(barFile), Schemas.untitled)); - service.registerFolderBackupSync(fooFile); - service.registerFolderBackupSync(barFile); - await service.initialize(); - assertEqualUris(service.getFolderBackupPaths(), []); - assert.ok(!fs.existsSync(service.toBackupPath(fooFile))); - assert.ok(!fs.existsSync(service.toBackupPath(barFile))); + // 3) backup workspace path exists with empty folders within + fs.mkdirSync(service.toBackupPath(fooFile)); + fs.mkdirSync(service.toBackupPath(barFile)); + fs.mkdirSync(path.join(service.toBackupPath(fooFile), Schemas.file)); + fs.mkdirSync(path.join(service.toBackupPath(barFile), Schemas.untitled)); + service.registerFolderBackupSync(fooFile); + service.registerFolderBackupSync(barFile); + await service.initialize(); + assertEqualUris(service.getFolderBackupPaths(), []); + assert.ok(!fs.existsSync(service.toBackupPath(fooFile))); + assert.ok(!fs.existsSync(service.toBackupPath(barFile))); - // 4) backup workspace path points to a workspace that no longer exists - // so it should convert the backup worspace to an empty workspace backup - const fileBackups = path.join(service.toBackupPath(fooFile), Schemas.file); - fs.mkdirSync(service.toBackupPath(fooFile)); - fs.mkdirSync(service.toBackupPath(barFile)); - fs.mkdirSync(fileBackups); - service.registerFolderBackupSync(fooFile); - assert.strictEqual(service.getFolderBackupPaths().length, 1); - assert.strictEqual(service.getEmptyWindowBackupPaths().length, 0); - fs.writeFileSync(path.join(fileBackups, 'backup.txt'), ''); - await service.initialize(); - assert.strictEqual(service.getFolderBackupPaths().length, 0); - assert.strictEqual(service.getEmptyWindowBackupPaths().length, 1); + // 4) backup workspace path points to a workspace that no longer exists + // so it should convert the backup worspace to an empty workspace backup + const fileBackups = path.join(service.toBackupPath(fooFile), Schemas.file); + fs.mkdirSync(service.toBackupPath(fooFile)); + fs.mkdirSync(service.toBackupPath(barFile)); + fs.mkdirSync(fileBackups); + service.registerFolderBackupSync(fooFile); + assert.strictEqual(service.getFolderBackupPaths().length, 1); + assert.strictEqual(service.getEmptyWindowBackupPaths().length, 0); + fs.writeFileSync(path.join(fileBackups, 'backup.txt'), ''); + await service.initialize(); + assert.strictEqual(service.getFolderBackupPaths().length, 0); + assert.strictEqual(service.getEmptyWindowBackupPaths().length, 1); + }); }); test('service validates backup workspaces on startup and cleans up (root workspaces)', async function () { + runWithFakedTimers({}, async () => { - // 1) backup workspace path does not exist - service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(fooFile.fsPath)); - service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(barFile.fsPath)); - await service.initialize(); - assert.deepStrictEqual(service.getWorkspaceBackups(), []); + // 1) backup workspace path does not exist + service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(fooFile.fsPath)); + service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(barFile.fsPath)); + await service.initialize(); + assert.deepStrictEqual(service.getWorkspaceBackups(), []); - // 2) backup workspace path exists with empty contents within - fs.mkdirSync(service.toBackupPath(fooFile)); - fs.mkdirSync(service.toBackupPath(barFile)); - service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(fooFile.fsPath)); - service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(barFile.fsPath)); - await service.initialize(); - assert.deepStrictEqual(service.getWorkspaceBackups(), []); - assert.ok(!fs.existsSync(service.toBackupPath(fooFile))); - assert.ok(!fs.existsSync(service.toBackupPath(barFile))); + // 2) backup workspace path exists with empty contents within + fs.mkdirSync(service.toBackupPath(fooFile)); + fs.mkdirSync(service.toBackupPath(barFile)); + service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(fooFile.fsPath)); + service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(barFile.fsPath)); + await service.initialize(); + assert.deepStrictEqual(service.getWorkspaceBackups(), []); + assert.ok(!fs.existsSync(service.toBackupPath(fooFile))); + assert.ok(!fs.existsSync(service.toBackupPath(barFile))); - // 3) backup workspace path exists with empty folders within - fs.mkdirSync(service.toBackupPath(fooFile)); - fs.mkdirSync(service.toBackupPath(barFile)); - fs.mkdirSync(path.join(service.toBackupPath(fooFile), Schemas.file)); - fs.mkdirSync(path.join(service.toBackupPath(barFile), Schemas.untitled)); - service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(fooFile.fsPath)); - service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(barFile.fsPath)); - await service.initialize(); - assert.deepStrictEqual(service.getWorkspaceBackups(), []); - assert.ok(!fs.existsSync(service.toBackupPath(fooFile))); - assert.ok(!fs.existsSync(service.toBackupPath(barFile))); + // 3) backup workspace path exists with empty folders within + fs.mkdirSync(service.toBackupPath(fooFile)); + fs.mkdirSync(service.toBackupPath(barFile)); + fs.mkdirSync(path.join(service.toBackupPath(fooFile), Schemas.file)); + fs.mkdirSync(path.join(service.toBackupPath(barFile), Schemas.untitled)); + service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(fooFile.fsPath)); + service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(barFile.fsPath)); + await service.initialize(); + assert.deepStrictEqual(service.getWorkspaceBackups(), []); + assert.ok(!fs.existsSync(service.toBackupPath(fooFile))); + assert.ok(!fs.existsSync(service.toBackupPath(barFile))); - // 4) backup workspace path points to a workspace that no longer exists - // so it should convert the backup worspace to an empty workspace backup - const fileBackups = path.join(service.toBackupPath(fooFile), Schemas.file); - fs.mkdirSync(service.toBackupPath(fooFile)); - fs.mkdirSync(service.toBackupPath(barFile)); - fs.mkdirSync(fileBackups); - service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(fooFile.fsPath)); - assert.strictEqual(service.getWorkspaceBackups().length, 1); - assert.strictEqual(service.getEmptyWindowBackupPaths().length, 0); - fs.writeFileSync(path.join(fileBackups, 'backup.txt'), ''); - await service.initialize(); - assert.strictEqual(service.getWorkspaceBackups().length, 0); - assert.strictEqual(service.getEmptyWindowBackupPaths().length, 1); + // 4) backup workspace path points to a workspace that no longer exists + // so it should convert the backup worspace to an empty workspace backup + const fileBackups = path.join(service.toBackupPath(fooFile), Schemas.file); + fs.mkdirSync(service.toBackupPath(fooFile)); + fs.mkdirSync(service.toBackupPath(barFile)); + fs.mkdirSync(fileBackups); + service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(fooFile.fsPath)); + assert.strictEqual(service.getWorkspaceBackups().length, 1); + assert.strictEqual(service.getEmptyWindowBackupPaths().length, 0); + fs.writeFileSync(path.join(fileBackups, 'backup.txt'), ''); + await service.initialize(); + assert.strictEqual(service.getWorkspaceBackups().length, 0); + assert.strictEqual(service.getEmptyWindowBackupPaths().length, 1); + }); }); test('service supports to migrate backup data from another location', () => { From 32744a08fb90b549696d89d865b5ba3095946736 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 25 Oct 2021 12:06:07 +0200 Subject: [PATCH 17/24] Fixes #135440 by allowing the preselection range to be longer than the range of the preselected item. --- .../suggestWidgetInlineCompletionProvider.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/inlineCompletions/suggestWidgetInlineCompletionProvider.ts b/src/vs/editor/contrib/inlineCompletions/suggestWidgetInlineCompletionProvider.ts index 9114ec0a89b..b55501d6f62 100644 --- a/src/vs/editor/contrib/inlineCompletions/suggestWidgetInlineCompletionProvider.ts +++ b/src/vs/editor/contrib/inlineCompletions/suggestWidgetInlineCompletionProvider.ts @@ -94,7 +94,7 @@ export class SuggestWidgetInlineCompletionProvider extends Disposable { return undefined; } const valid = - normalizedSuggestItem.range.equalsRange(normalizedItemToPreselect.range) && + rangeStartsWith(normalizedItemToPreselect.range, normalizedSuggestItem.range) && normalizedItemToPreselect.text.startsWith(normalizedSuggestItem.text); return { index, valid, prefixLength: normalizedSuggestItem.text.length, suggestItem }; }) @@ -190,6 +190,16 @@ export class SuggestWidgetInlineCompletionProvider extends Disposable { } } +function rangeStartsWith(rangeToTest: Range, prefix: Range): boolean { + return ( + rangeToTest.startLineNumber === prefix.startLineNumber && + rangeToTest.startColumn === prefix.startColumn && + (rangeToTest.endLineNumber < prefix.endLineNumber || + (rangeToTest.endLineNumber === prefix.endLineNumber && + rangeToTest.endColumn <= prefix.endColumn)) + ); +} + function suggestionToInlineCompletion(suggestController: SuggestController, position: Position, item: CompletionItem, toggleMode: boolean): NormalizedInlineCompletion | undefined { // additionalTextEdits might not be resolved here, this could be problematic. if (Array.isArray(item.completion.additionalTextEdits) && item.completion.additionalTextEdits.length > 0) { From bda864be2869a80b621d3659e45962fb2d172d3c Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 25 Oct 2021 14:24:14 +0200 Subject: [PATCH 18/24] Fix #134909 --- .../sharedProcess/sharedProcessMain.ts | 4 ++++ src/vs/code/electron-main/app.ts | 6 ++++-- .../common/userConfigurationFileService.ts | 18 +++++++++++------- .../common/abstractSynchronizer.ts | 6 +++++- .../userDataSync/common/settingsSync.ts | 8 +++++++- 5 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index ba7e600b97c..d5faed937bd 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -94,6 +94,7 @@ import { ipcSharedProcessTunnelChannelName, ISharedProcessTunnelService } from ' import { SharedProcessTunnelService } from 'vs/platform/remote/node/sharedProcessTunnelService'; import { ipcSharedProcessWorkerChannelName, ISharedProcessWorkerConfiguration, ISharedProcessWorkerService } from 'vs/platform/sharedProcess/common/sharedProcessWorkerService'; import { SharedProcessWorkerService } from 'vs/platform/sharedProcess/electron-browser/sharedProcessWorkerService'; +import { IUserConfigurationFileService, UserConfigurationFileServiceId } from 'vs/platform/configuration/common/userConfigurationFileService'; class SharedProcessMain extends Disposable { @@ -218,6 +219,9 @@ class SharedProcessMain extends Disposable { storageService.initialize() ]); + // User Configuration File + services.set(IUserConfigurationFileService, ProxyChannel.toService(mainProcessService.getChannel(UserConfigurationFileServiceId))); + // Request services.set(IRequestService, new SyncDescriptor(RequestService)); diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index c4574912081..c284c8aed67 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -567,8 +567,10 @@ export class CodeApplication extends Disposable { const fileSystemProviderChannel = new DiskFileSystemProviderChannel(diskFileSystemProvider, this.logService); mainProcessElectronServer.registerChannel('localFilesystem', fileSystemProviderChannel); - // Configuration - mainProcessElectronServer.registerChannel(UserConfigurationFileServiceId, ProxyChannel.fromService(new UserConfigurationFileService(this.environmentMainService, this.fileService, this.logService))); + // User Configuration File + const userConfigurationFileService = new UserConfigurationFileService(this.environmentMainService, this.fileService, this.logService); + mainProcessElectronServer.registerChannel(UserConfigurationFileServiceId, ProxyChannel.fromService(userConfigurationFileService)); + sharedProcessClient.then(client => client.registerChannel(UserConfigurationFileServiceId, ProxyChannel.fromService(userConfigurationFileService))); // Update const updateChannel = new UpdateChannel(accessor.get(IUpdateService)); diff --git a/src/vs/platform/configuration/common/userConfigurationFileService.ts b/src/vs/platform/configuration/common/userConfigurationFileService.ts index c826c7fbd87..12245ff5e55 100644 --- a/src/vs/platform/configuration/common/userConfigurationFileService.ts +++ b/src/vs/platform/configuration/common/userConfigurationFileService.ts @@ -8,9 +8,8 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { JSONPath, parse, ParseError } from 'vs/base/common/json'; import { setProperty } from 'vs/base/common/jsonEdit'; import { Edit, FormattingOptions } from 'vs/base/common/jsonFormatter'; -import { URI } from 'vs/base/common/uri'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; +import { FileOperationError, FileOperationResult, IFileService, IWriteFileOptions } from 'vs/platform/files/common/files'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; @@ -31,6 +30,7 @@ export interface IUserConfigurationFileService { readonly _serviceBrand: undefined; updateSettings(value: IJSONValue, formattingOptions: FormattingOptions): Promise; + write(value: VSBuffer, options?: IWriteFileOptions): Promise; } export class UserConfigurationFileService implements IUserConfigurationFileService { @@ -48,12 +48,12 @@ export class UserConfigurationFileService implements IUserConfigurationFileServi } async updateSettings(value: IJSONValue, formattingOptions: FormattingOptions): Promise { - return this.queue.queue(() => this.doWrite(this.environmentService.settingsResource, value, formattingOptions)); // queue up writes to prevent race conditions + return this.queue.queue(() => this.doWrite(value, formattingOptions)); // queue up writes to prevent race conditions } - private async doWrite(resource: URI, jsonValue: IJSONValue, formattingOptions: FormattingOptions): Promise { - this.logService.trace(`${UserConfigurationFileServiceId}#write`, resource.toString(), jsonValue); - const { value, mtime, etag } = await this.fileService.readFile(resource, { atomic: true }); + private async doWrite(jsonValue: IJSONValue, formattingOptions: FormattingOptions): Promise { + this.logService.trace(`${UserConfigurationFileServiceId}#write`, this.environmentService.settingsResource.toString(), jsonValue); + const { value, mtime, etag } = await this.fileService.readFile(this.environmentService.settingsResource, { atomic: true }); let content = value.toString(); const parseErrors: ParseError[] = []; @@ -66,7 +66,7 @@ export class UserConfigurationFileService implements IUserConfigurationFileServi if (edit) { content = content.substring(0, edit.offset) + edit.content + content.substring(edit.offset + edit.length); try { - await this.fileService.writeFile(resource, VSBuffer.fromString(content), { etag, mtime }); + await this.write(VSBuffer.fromString(content), { etag, mtime }); } catch (error) { if ((error).fileOperationResult === FileOperationResult.FILE_MODIFIED_SINCE) { throw new Error(UserConfigurationErrorCode.ERROR_FILE_MODIFIED_SINCE); @@ -75,6 +75,10 @@ export class UserConfigurationFileService implements IUserConfigurationFileServi } } + async write(content: VSBuffer, options?: IWriteFileOptions): Promise { + await this.fileService.writeFile(this.environmentService.settingsResource, content, options); + } + private getEdits({ value, path }: IJSONValue, modelContent: string, formattingOptions: FormattingOptions): Edit[] { if (path.length) { return setProperty(modelContent, path, value, formattingOptions); diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts index f0dc7fdef4a..6b15b6c5549 100644 --- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -760,7 +760,7 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser { try { if (oldContent) { // file exists already - await this.fileService.writeFile(this.file, VSBuffer.fromString(newContent), force ? undefined : oldContent); + await this.writeFileContent(newContent, oldContent, force); } else { // file does not exist await this.fileService.createFile(this.file, VSBuffer.fromString(newContent), { overwrite: force }); @@ -775,6 +775,10 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser { } } + protected async writeFileContent(newContent: string, oldContent: IFileContent, force: boolean): Promise { + await this.fileService.writeFile(this.file, VSBuffer.fromString(newContent), force ? undefined : oldContent); + } + private onFileChanges(e: FileChangesEvent): void { if (!e.contains(this.file)) { return; diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index 31da84131e4..bb7505765f2 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -12,9 +12,10 @@ import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ConfigurationModelParser } from 'vs/platform/configuration/common/configurationModels'; +import { IUserConfigurationFileService } from 'vs/platform/configuration/common/userConfigurationFileService'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; +import { FileOperationError, FileOperationResult, IFileContent, IFileService } from 'vs/platform/files/common/files'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { AbstractInitializer, AbstractJsonFileSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer'; @@ -62,6 +63,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement @IUserDataSyncResourceEnablementService userDataSyncResourceEnablementService: IUserDataSyncResourceEnablementService, @ITelemetryService telemetryService: ITelemetryService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, + @IUserConfigurationFileService private readonly userConfigurationFileService: IUserConfigurationFileService, ) { super(environmentService.settingsResource, SyncResource.Settings, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncResourceEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService); } @@ -325,6 +327,10 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement return getIgnoredSettings(defaultIgnoredSettings, this.configurationService, content); } + protected override async writeFileContent(newContent: string, oldContent: IFileContent, force: boolean): Promise { + await this.userConfigurationFileService.write(VSBuffer.fromString(newContent), force ? undefined : { etag: oldContent.etag, mtime: oldContent.mtime }); + } + private validateContent(content: string): void { if (this.hasErrors(content)) { throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync settings as there are errors/warning in settings file."), UserDataSyncErrorCode.LocalInvalidContent, this.resource); From 0f7982a344dfc182b61338fbbcad0912d2fb01f1 Mon Sep 17 00:00:00 2001 From: Issam Maghni Date: Mon, 25 Oct 2021 08:40:32 -0400 Subject: [PATCH 19/24] Fix mkdir arguments order (#135182) When lauching with `env POSIXLY_CORRECT=y code [...]`, VSCode creates two directories named `-m` and `700`. This is because the order of arguments, in a POSIX strict environment, matters. --- resources/linux/snap/electron-launch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/linux/snap/electron-launch b/resources/linux/snap/electron-launch index 9f4eb6a23b8..87011928b49 100755 --- a/resources/linux/snap/electron-launch +++ b/resources/linux/snap/electron-launch @@ -29,6 +29,6 @@ if [ -f "$SNAP/usr/lib/$ARCH/gdk-pixbuf-2.0/gdk-pixbuf-query-loaders" ]; then fi # Create $XDG_RUNTIME_DIR if not exists (to be removed when https://pad.lv/1656340 is fixed) -[ -n "$XDG_RUNTIME_DIR" ] && mkdir -p "$XDG_RUNTIME_DIR" -m 700 +[ -n "$XDG_RUNTIME_DIR" ] && mkdir -p -m 700 "$XDG_RUNTIME_DIR" exec "$@" From 2bf992d858f554c3b9da9e586023d5d071ed665c Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 25 Oct 2021 14:47:09 +0200 Subject: [PATCH 20/24] tests - more `runWithFakedTimers` usage (#135075) --- .../parts/storage/test/node/storage.test.ts | 366 +++++++++--------- .../electron-main/backupMainService.test.ts | 157 ++++---- .../test/browser/storageService.test.ts | 4 +- 3 files changed, 265 insertions(+), 262 deletions(-) diff --git a/src/vs/base/parts/storage/test/node/storage.test.ts b/src/vs/base/parts/storage/test/node/storage.test.ts index 5b4af59bcb1..824d6b22f52 100644 --- a/src/vs/base/parts/storage/test/node/storage.test.ts +++ b/src/vs/base/parts/storage/test/node/storage.test.ts @@ -13,6 +13,7 @@ import { generateUuid } from 'vs/base/common/uuid'; import { Promises } from 'vs/base/node/pfs'; import { isStorageItemsChangeEvent, IStorageDatabase, IStorageItemsChangeEvent, Storage } from 'vs/base/parts/storage/common/storage'; import { ISQLiteStorageDatabaseOptions, SQLiteStorageDatabase } from 'vs/base/parts/storage/node/storage'; +import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils'; flakySuite('Storage Library', function () { @@ -29,139 +30,142 @@ flakySuite('Storage Library', function () { return Promises.rm(testDir); }); - test('basics', async () => { - const storage = new Storage(new SQLiteStorageDatabase(join(testDir, 'storage.db'))); + test('basics', () => { + return runWithFakedTimers({}, async function () { + const storage = new Storage(new SQLiteStorageDatabase(join(testDir, 'storage.db'))); - await storage.init(); + await storage.init(); - // Empty fallbacks - strictEqual(storage.get('foo', 'bar'), 'bar'); - strictEqual(storage.getNumber('foo', 55), 55); - strictEqual(storage.getBoolean('foo', true), true); + // Empty fallbacks + strictEqual(storage.get('foo', 'bar'), 'bar'); + strictEqual(storage.getNumber('foo', 55), 55); + strictEqual(storage.getBoolean('foo', true), true); - let changes = new Set(); - storage.onDidChangeStorage(key => { - changes.add(key); + let changes = new Set(); + storage.onDidChangeStorage(key => { + changes.add(key); + }); + + await storage.whenFlushed(); // returns immediately when no pending updates + + // Simple updates + const set1Promise = storage.set('bar', 'foo'); + const set2Promise = storage.set('barNumber', 55); + const set3Promise = storage.set('barBoolean', true); + + let flushPromiseResolved = false; + storage.whenFlushed().then(() => flushPromiseResolved = true); + + strictEqual(storage.get('bar'), 'foo'); + strictEqual(storage.getNumber('barNumber'), 55); + strictEqual(storage.getBoolean('barBoolean'), true); + + strictEqual(changes.size, 3); + ok(changes.has('bar')); + ok(changes.has('barNumber')); + ok(changes.has('barBoolean')); + + let setPromiseResolved = false; + await Promise.all([set1Promise, set2Promise, set3Promise]).then(() => setPromiseResolved = true); + strictEqual(setPromiseResolved, true); + strictEqual(flushPromiseResolved, true); + + changes = new Set(); + + // Does not trigger events for same update values + storage.set('bar', 'foo'); + storage.set('barNumber', 55); + storage.set('barBoolean', true); + strictEqual(changes.size, 0); + + // Simple deletes + const delete1Promise = storage.delete('bar'); + const delete2Promise = storage.delete('barNumber'); + const delete3Promise = storage.delete('barBoolean'); + + ok(!storage.get('bar')); + ok(!storage.getNumber('barNumber')); + ok(!storage.getBoolean('barBoolean')); + + strictEqual(changes.size, 3); + ok(changes.has('bar')); + ok(changes.has('barNumber')); + ok(changes.has('barBoolean')); + + changes = new Set(); + + // Does not trigger events for same delete values + storage.delete('bar'); + storage.delete('barNumber'); + storage.delete('barBoolean'); + strictEqual(changes.size, 0); + + let deletePromiseResolved = false; + await Promise.all([delete1Promise, delete2Promise, delete3Promise]).then(() => deletePromiseResolved = true); + strictEqual(deletePromiseResolved, true); + + await storage.close(); + await storage.close(); // it is ok to call this multiple times }); - - await storage.whenFlushed(); // returns immediately when no pending updates - - // Simple updates - const set1Promise = storage.set('bar', 'foo'); - const set2Promise = storage.set('barNumber', 55); - const set3Promise = storage.set('barBoolean', true); - - let flushPromiseResolved = false; - storage.whenFlushed().then(() => flushPromiseResolved = true); - - strictEqual(storage.get('bar'), 'foo'); - strictEqual(storage.getNumber('barNumber'), 55); - strictEqual(storage.getBoolean('barBoolean'), true); - - strictEqual(changes.size, 3); - ok(changes.has('bar')); - ok(changes.has('barNumber')); - ok(changes.has('barBoolean')); - - let setPromiseResolved = false; - await Promise.all([set1Promise, set2Promise, set3Promise]).then(() => setPromiseResolved = true); - strictEqual(setPromiseResolved, true); - strictEqual(flushPromiseResolved, true); - - changes = new Set(); - - // Does not trigger events for same update values - storage.set('bar', 'foo'); - storage.set('barNumber', 55); - storage.set('barBoolean', true); - strictEqual(changes.size, 0); - - // Simple deletes - const delete1Promise = storage.delete('bar'); - const delete2Promise = storage.delete('barNumber'); - const delete3Promise = storage.delete('barBoolean'); - - ok(!storage.get('bar')); - ok(!storage.getNumber('barNumber')); - ok(!storage.getBoolean('barBoolean')); - - strictEqual(changes.size, 3); - ok(changes.has('bar')); - ok(changes.has('barNumber')); - ok(changes.has('barBoolean')); - - changes = new Set(); - - // Does not trigger events for same delete values - storage.delete('bar'); - storage.delete('barNumber'); - storage.delete('barBoolean'); - strictEqual(changes.size, 0); - - let deletePromiseResolved = false; - await Promise.all([delete1Promise, delete2Promise, delete3Promise]).then(() => deletePromiseResolved = true); - strictEqual(deletePromiseResolved, true); - - await storage.close(); - await storage.close(); // it is ok to call this multiple times }); - test('external changes', async () => { + test('external changes', () => { + return runWithFakedTimers({}, async function () { + class TestSQLiteStorageDatabase extends SQLiteStorageDatabase { + private readonly _onDidChangeItemsExternal = new Emitter(); + override get onDidChangeItemsExternal(): Event { return this._onDidChangeItemsExternal.event; } - class TestSQLiteStorageDatabase extends SQLiteStorageDatabase { - private readonly _onDidChangeItemsExternal = new Emitter(); - override get onDidChangeItemsExternal(): Event { return this._onDidChangeItemsExternal.event; } - - fireDidChangeItemsExternal(event: IStorageItemsChangeEvent): void { - this._onDidChangeItemsExternal.fire(event); + fireDidChangeItemsExternal(event: IStorageItemsChangeEvent): void { + this._onDidChangeItemsExternal.fire(event); + } } - } - const database = new TestSQLiteStorageDatabase(join(testDir, 'storage.db')); - const storage = new Storage(database); + const database = new TestSQLiteStorageDatabase(join(testDir, 'storage.db')); + const storage = new Storage(database); - let changes = new Set(); - storage.onDidChangeStorage(key => { - changes.add(key); + let changes = new Set(); + storage.onDidChangeStorage(key => { + changes.add(key); + }); + + await storage.init(); + + await storage.set('foo', 'bar'); + ok(changes.has('foo')); + changes.clear(); + + // Nothing happens if changing to same value + const changed = new Map(); + changed.set('foo', 'bar'); + database.fireDidChangeItemsExternal({ changed }); + strictEqual(changes.size, 0); + + // Change is accepted if valid + changed.set('foo', 'bar1'); + database.fireDidChangeItemsExternal({ changed }); + ok(changes.has('foo')); + strictEqual(storage.get('foo'), 'bar1'); + changes.clear(); + + // Delete is accepted + const deleted = new Set(['foo']); + database.fireDidChangeItemsExternal({ deleted }); + ok(changes.has('foo')); + strictEqual(storage.get('foo', undefined), undefined); + changes.clear(); + + // Nothing happens if changing to same value + database.fireDidChangeItemsExternal({ deleted }); + strictEqual(changes.size, 0); + + strictEqual(isStorageItemsChangeEvent({ changed }), true); + strictEqual(isStorageItemsChangeEvent({ deleted }), true); + strictEqual(isStorageItemsChangeEvent({ changed, deleted }), true); + strictEqual(isStorageItemsChangeEvent(undefined), false); + strictEqual(isStorageItemsChangeEvent({ changed: 'yes', deleted: false }), false); + + await storage.close(); }); - - await storage.init(); - - await storage.set('foo', 'bar'); - ok(changes.has('foo')); - changes.clear(); - - // Nothing happens if changing to same value - const changed = new Map(); - changed.set('foo', 'bar'); - database.fireDidChangeItemsExternal({ changed }); - strictEqual(changes.size, 0); - - // Change is accepted if valid - changed.set('foo', 'bar1'); - database.fireDidChangeItemsExternal({ changed }); - ok(changes.has('foo')); - strictEqual(storage.get('foo'), 'bar1'); - changes.clear(); - - // Delete is accepted - const deleted = new Set(['foo']); - database.fireDidChangeItemsExternal({ deleted }); - ok(changes.has('foo')); - strictEqual(storage.get('foo', undefined), undefined); - changes.clear(); - - // Nothing happens if changing to same value - database.fireDidChangeItemsExternal({ deleted }); - strictEqual(changes.size, 0); - - strictEqual(isStorageItemsChangeEvent({ changed }), true); - strictEqual(isStorageItemsChangeEvent({ deleted }), true); - strictEqual(isStorageItemsChangeEvent({ changed, deleted }), true); - strictEqual(isStorageItemsChangeEvent(undefined), false); - strictEqual(isStorageItemsChangeEvent({ changed: 'yes', deleted: false }), false); - - await storage.close(); }); test('close flushes data', async () => { @@ -218,72 +222,76 @@ flakySuite('Storage Library', function () { await storage.close(); }); - test('conflicting updates', async () => { - let storage = new Storage(new SQLiteStorageDatabase(join(testDir, 'storage.db'))); - await storage.init(); + test('conflicting updates', () => { + return runWithFakedTimers({}, async function () { + let storage = new Storage(new SQLiteStorageDatabase(join(testDir, 'storage.db'))); + await storage.init(); - let changes = new Set(); - storage.onDidChangeStorage(key => { - changes.add(key); + let changes = new Set(); + storage.onDidChangeStorage(key => { + changes.add(key); + }); + + const set1Promise = storage.set('foo', 'bar1'); + const set2Promise = storage.set('foo', 'bar2'); + const set3Promise = storage.set('foo', 'bar3'); + + let flushPromiseResolved = false; + storage.whenFlushed().then(() => flushPromiseResolved = true); + + strictEqual(storage.get('foo'), 'bar3'); + strictEqual(changes.size, 1); + ok(changes.has('foo')); + + let setPromiseResolved = false; + await Promise.all([set1Promise, set2Promise, set3Promise]).then(() => setPromiseResolved = true); + ok(setPromiseResolved); + ok(flushPromiseResolved); + + changes = new Set(); + + const set4Promise = storage.set('bar', 'foo'); + const delete1Promise = storage.delete('bar'); + + ok(!storage.get('bar')); + + strictEqual(changes.size, 1); + ok(changes.has('bar')); + + let setAndDeletePromiseResolved = false; + await Promise.all([set4Promise, delete1Promise]).then(() => setAndDeletePromiseResolved = true); + ok(setAndDeletePromiseResolved); + + await storage.close(); }); - - const set1Promise = storage.set('foo', 'bar1'); - const set2Promise = storage.set('foo', 'bar2'); - const set3Promise = storage.set('foo', 'bar3'); - - let flushPromiseResolved = false; - storage.whenFlushed().then(() => flushPromiseResolved = true); - - strictEqual(storage.get('foo'), 'bar3'); - strictEqual(changes.size, 1); - ok(changes.has('foo')); - - let setPromiseResolved = false; - await Promise.all([set1Promise, set2Promise, set3Promise]).then(() => setPromiseResolved = true); - ok(setPromiseResolved); - ok(flushPromiseResolved); - - changes = new Set(); - - const set4Promise = storage.set('bar', 'foo'); - const delete1Promise = storage.delete('bar'); - - ok(!storage.get('bar')); - - strictEqual(changes.size, 1); - ok(changes.has('bar')); - - let setAndDeletePromiseResolved = false; - await Promise.all([set4Promise, delete1Promise]).then(() => setAndDeletePromiseResolved = true); - ok(setAndDeletePromiseResolved); - - await storage.close(); }); test('corrupt DB recovers', async () => { - const storageFile = join(testDir, 'storage.db'); + return runWithFakedTimers({}, async function () { + const storageFile = join(testDir, 'storage.db'); - let storage = new Storage(new SQLiteStorageDatabase(storageFile)); - await storage.init(); + let storage = new Storage(new SQLiteStorageDatabase(storageFile)); + await storage.init(); - await storage.set('bar', 'foo'); + await storage.set('bar', 'foo'); - await Promises.writeFile(storageFile, 'This is a broken DB'); + await Promises.writeFile(storageFile, 'This is a broken DB'); - await storage.set('foo', 'bar'); + await storage.set('foo', 'bar'); - strictEqual(storage.get('bar'), 'foo'); - strictEqual(storage.get('foo'), 'bar'); + strictEqual(storage.get('bar'), 'foo'); + strictEqual(storage.get('foo'), 'bar'); - await storage.close(); + await storage.close(); - storage = new Storage(new SQLiteStorageDatabase(storageFile)); - await storage.init(); + storage = new Storage(new SQLiteStorageDatabase(storageFile)); + await storage.init(); - strictEqual(storage.get('bar'), 'foo'); - strictEqual(storage.get('foo'), 'bar'); + strictEqual(storage.get('bar'), 'foo'); + strictEqual(storage.get('foo'), 'bar'); - await storage.close(); + await storage.close(); + }); }); }); @@ -657,27 +665,27 @@ flakySuite('SQLite Storage Library', function () { storage.set('foo', 'bar'); storage.set('some/foo/path', 'some/bar/path'); - await timeout(10); + await timeout(2); storage.set('foo1', 'bar'); storage.set('some/foo1/path', 'some/bar/path'); - await timeout(10); + await timeout(2); storage.set('foo2', 'bar'); storage.set('some/foo2/path', 'some/bar/path'); - await timeout(10); + await timeout(2); storage.delete('foo1'); storage.delete('some/foo1/path'); - await timeout(10); + await timeout(2); storage.delete('foo4'); storage.delete('some/foo4/path'); - await timeout(70); + await timeout(5); storage.set('foo3', 'bar'); await storage.set('some/foo3/path', 'some/bar/path'); diff --git a/src/vs/platform/backup/test/electron-main/backupMainService.test.ts b/src/vs/platform/backup/test/electron-main/backupMainService.test.ts index f4079d07649..9c9e8e4c712 100644 --- a/src/vs/platform/backup/test/electron-main/backupMainService.test.ts +++ b/src/vs/platform/backup/test/electron-main/backupMainService.test.ts @@ -13,7 +13,6 @@ import * as platform from 'vs/base/common/platform'; import { isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import * as pfs from 'vs/base/node/pfs'; -import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils'; import { IWorkspaceBackupInfo } from 'vs/platform/backup/electron-main/backup'; import { BackupMainService } from 'vs/platform/backup/electron-main/backupMainService'; @@ -137,97 +136,93 @@ flakySuite('BackupMainService', () => { }); test('service validates backup workspaces on startup and cleans up (folder workspaces)', async function () { - runWithFakedTimers({}, async () => { - // 1) backup workspace path does not exist - service.registerFolderBackupSync(fooFile); - service.registerFolderBackupSync(barFile); - await service.initialize(); - assertEqualUris(service.getFolderBackupPaths(), []); + // 1) backup workspace path does not exist + service.registerFolderBackupSync(fooFile); + service.registerFolderBackupSync(barFile); + await service.initialize(); + assertEqualUris(service.getFolderBackupPaths(), []); - // 2) backup workspace path exists with empty contents within - fs.mkdirSync(service.toBackupPath(fooFile)); - fs.mkdirSync(service.toBackupPath(barFile)); - service.registerFolderBackupSync(fooFile); - service.registerFolderBackupSync(barFile); - await service.initialize(); - assertEqualUris(service.getFolderBackupPaths(), []); - assert.ok(!fs.existsSync(service.toBackupPath(fooFile))); - assert.ok(!fs.existsSync(service.toBackupPath(barFile))); + // 2) backup workspace path exists with empty contents within + fs.mkdirSync(service.toBackupPath(fooFile)); + fs.mkdirSync(service.toBackupPath(barFile)); + service.registerFolderBackupSync(fooFile); + service.registerFolderBackupSync(barFile); + await service.initialize(); + assertEqualUris(service.getFolderBackupPaths(), []); + assert.ok(!fs.existsSync(service.toBackupPath(fooFile))); + assert.ok(!fs.existsSync(service.toBackupPath(barFile))); - // 3) backup workspace path exists with empty folders within - fs.mkdirSync(service.toBackupPath(fooFile)); - fs.mkdirSync(service.toBackupPath(barFile)); - fs.mkdirSync(path.join(service.toBackupPath(fooFile), Schemas.file)); - fs.mkdirSync(path.join(service.toBackupPath(barFile), Schemas.untitled)); - service.registerFolderBackupSync(fooFile); - service.registerFolderBackupSync(barFile); - await service.initialize(); - assertEqualUris(service.getFolderBackupPaths(), []); - assert.ok(!fs.existsSync(service.toBackupPath(fooFile))); - assert.ok(!fs.existsSync(service.toBackupPath(barFile))); + // 3) backup workspace path exists with empty folders within + fs.mkdirSync(service.toBackupPath(fooFile)); + fs.mkdirSync(service.toBackupPath(barFile)); + fs.mkdirSync(path.join(service.toBackupPath(fooFile), Schemas.file)); + fs.mkdirSync(path.join(service.toBackupPath(barFile), Schemas.untitled)); + service.registerFolderBackupSync(fooFile); + service.registerFolderBackupSync(barFile); + await service.initialize(); + assertEqualUris(service.getFolderBackupPaths(), []); + assert.ok(!fs.existsSync(service.toBackupPath(fooFile))); + assert.ok(!fs.existsSync(service.toBackupPath(barFile))); - // 4) backup workspace path points to a workspace that no longer exists - // so it should convert the backup worspace to an empty workspace backup - const fileBackups = path.join(service.toBackupPath(fooFile), Schemas.file); - fs.mkdirSync(service.toBackupPath(fooFile)); - fs.mkdirSync(service.toBackupPath(barFile)); - fs.mkdirSync(fileBackups); - service.registerFolderBackupSync(fooFile); - assert.strictEqual(service.getFolderBackupPaths().length, 1); - assert.strictEqual(service.getEmptyWindowBackupPaths().length, 0); - fs.writeFileSync(path.join(fileBackups, 'backup.txt'), ''); - await service.initialize(); - assert.strictEqual(service.getFolderBackupPaths().length, 0); - assert.strictEqual(service.getEmptyWindowBackupPaths().length, 1); - }); + // 4) backup workspace path points to a workspace that no longer exists + // so it should convert the backup worspace to an empty workspace backup + const fileBackups = path.join(service.toBackupPath(fooFile), Schemas.file); + fs.mkdirSync(service.toBackupPath(fooFile)); + fs.mkdirSync(service.toBackupPath(barFile)); + fs.mkdirSync(fileBackups); + service.registerFolderBackupSync(fooFile); + assert.strictEqual(service.getFolderBackupPaths().length, 1); + assert.strictEqual(service.getEmptyWindowBackupPaths().length, 0); + fs.writeFileSync(path.join(fileBackups, 'backup.txt'), ''); + await service.initialize(); + assert.strictEqual(service.getFolderBackupPaths().length, 0); + assert.strictEqual(service.getEmptyWindowBackupPaths().length, 1); }); test('service validates backup workspaces on startup and cleans up (root workspaces)', async function () { - runWithFakedTimers({}, async () => { - // 1) backup workspace path does not exist - service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(fooFile.fsPath)); - service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(barFile.fsPath)); - await service.initialize(); - assert.deepStrictEqual(service.getWorkspaceBackups(), []); + // 1) backup workspace path does not exist + service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(fooFile.fsPath)); + service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(barFile.fsPath)); + await service.initialize(); + assert.deepStrictEqual(service.getWorkspaceBackups(), []); - // 2) backup workspace path exists with empty contents within - fs.mkdirSync(service.toBackupPath(fooFile)); - fs.mkdirSync(service.toBackupPath(barFile)); - service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(fooFile.fsPath)); - service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(barFile.fsPath)); - await service.initialize(); - assert.deepStrictEqual(service.getWorkspaceBackups(), []); - assert.ok(!fs.existsSync(service.toBackupPath(fooFile))); - assert.ok(!fs.existsSync(service.toBackupPath(barFile))); + // 2) backup workspace path exists with empty contents within + fs.mkdirSync(service.toBackupPath(fooFile)); + fs.mkdirSync(service.toBackupPath(barFile)); + service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(fooFile.fsPath)); + service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(barFile.fsPath)); + await service.initialize(); + assert.deepStrictEqual(service.getWorkspaceBackups(), []); + assert.ok(!fs.existsSync(service.toBackupPath(fooFile))); + assert.ok(!fs.existsSync(service.toBackupPath(barFile))); - // 3) backup workspace path exists with empty folders within - fs.mkdirSync(service.toBackupPath(fooFile)); - fs.mkdirSync(service.toBackupPath(barFile)); - fs.mkdirSync(path.join(service.toBackupPath(fooFile), Schemas.file)); - fs.mkdirSync(path.join(service.toBackupPath(barFile), Schemas.untitled)); - service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(fooFile.fsPath)); - service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(barFile.fsPath)); - await service.initialize(); - assert.deepStrictEqual(service.getWorkspaceBackups(), []); - assert.ok(!fs.existsSync(service.toBackupPath(fooFile))); - assert.ok(!fs.existsSync(service.toBackupPath(barFile))); + // 3) backup workspace path exists with empty folders within + fs.mkdirSync(service.toBackupPath(fooFile)); + fs.mkdirSync(service.toBackupPath(barFile)); + fs.mkdirSync(path.join(service.toBackupPath(fooFile), Schemas.file)); + fs.mkdirSync(path.join(service.toBackupPath(barFile), Schemas.untitled)); + service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(fooFile.fsPath)); + service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(barFile.fsPath)); + await service.initialize(); + assert.deepStrictEqual(service.getWorkspaceBackups(), []); + assert.ok(!fs.existsSync(service.toBackupPath(fooFile))); + assert.ok(!fs.existsSync(service.toBackupPath(barFile))); - // 4) backup workspace path points to a workspace that no longer exists - // so it should convert the backup worspace to an empty workspace backup - const fileBackups = path.join(service.toBackupPath(fooFile), Schemas.file); - fs.mkdirSync(service.toBackupPath(fooFile)); - fs.mkdirSync(service.toBackupPath(barFile)); - fs.mkdirSync(fileBackups); - service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(fooFile.fsPath)); - assert.strictEqual(service.getWorkspaceBackups().length, 1); - assert.strictEqual(service.getEmptyWindowBackupPaths().length, 0); - fs.writeFileSync(path.join(fileBackups, 'backup.txt'), ''); - await service.initialize(); - assert.strictEqual(service.getWorkspaceBackups().length, 0); - assert.strictEqual(service.getEmptyWindowBackupPaths().length, 1); - }); + // 4) backup workspace path points to a workspace that no longer exists + // so it should convert the backup worspace to an empty workspace backup + const fileBackups = path.join(service.toBackupPath(fooFile), Schemas.file); + fs.mkdirSync(service.toBackupPath(fooFile)); + fs.mkdirSync(service.toBackupPath(barFile)); + fs.mkdirSync(fileBackups); + service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(fooFile.fsPath)); + assert.strictEqual(service.getWorkspaceBackups().length, 1); + assert.strictEqual(service.getEmptyWindowBackupPaths().length, 0); + fs.writeFileSync(path.join(fileBackups, 'backup.txt'), ''); + await service.initialize(); + assert.strictEqual(service.getWorkspaceBackups().length, 0); + assert.strictEqual(service.getEmptyWindowBackupPaths().length, 1); }); test('service supports to migrate backup data from another location', () => { diff --git a/src/vs/platform/storage/test/browser/storageService.test.ts b/src/vs/platform/storage/test/browser/storageService.test.ts index 5c4665ad1d4..2b1ed5b22e6 100644 --- a/src/vs/platform/storage/test/browser/storageService.test.ts +++ b/src/vs/platform/storage/test/browser/storageService.test.ts @@ -67,8 +67,8 @@ flakySuite('StorageService (browser specific)', () => { disposables.clear(); }); - test('clear', async () => { - await runWithFakedTimers({ useFakeTimers: true }, async () => { + test('clear', () => { + return runWithFakedTimers({ useFakeTimers: true }, async () => { storageService.store('bar', 'foo', StorageScope.GLOBAL, StorageTarget.MACHINE); storageService.store('bar', 3, StorageScope.GLOBAL, StorageTarget.USER); storageService.store('bar', 'foo', StorageScope.WORKSPACE, StorageTarget.MACHINE); From 0e7d8a6cfd654725e7a25645cf11bf4677afdbbd Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 25 Oct 2021 14:50:23 +0200 Subject: [PATCH 21/24] Add vscode-remotehub repository --- .vscode/notebooks/my-work.github-issues | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/notebooks/my-work.github-issues b/.vscode/notebooks/my-work.github-issues index 1f66b716852..c521c15900d 100644 --- a/.vscode/notebooks/my-work.github-issues +++ b/.vscode/notebooks/my-work.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "// list of repos we work in\n$repos=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-references-view repo:microsoft/vscode-anycode repo:microsoft/vscode-hexeditor repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-livepreview\n\n// current milestone name\n$milestone=milestone:\"October 2021\"" + "value": "// list of repos we work in\n$repos=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-references-view repo:microsoft/vscode-anycode repo:microsoft/vscode-hexeditor repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-livepreview repo:microsoft/vscode-remotehub\n\n// current milestone name\n$milestone=milestone:\"October 2021\"" }, { "kind": 1, From 9a3bcbe4f7d4a1d9f2e7a6a7893065aed7743791 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 25 Oct 2021 15:18:43 +0200 Subject: [PATCH 22/24] editors - cleanup wording for editors/files with unsaved changes --- .../src/settingsDocumentHelper.ts | 2 +- src/vs/workbench/browser/contextkeys.ts | 2 +- .../browser/parts/editor/editorQuickAccess.ts | 4 ++-- .../workbench/browser/workbench.contribution.ts | 8 ++++---- src/vs/workbench/common/editor.ts | 2 +- src/vs/workbench/common/theme.ts | 8 ++++---- .../contrib/files/browser/files.contribution.ts | 16 ++++++++-------- .../search/browser/anythingQuickAccess.ts | 2 +- .../terminal/common/terminalConfiguration.ts | 2 +- .../electron-sandbox/actions/windowActions.ts | 2 +- .../common/configurationEditingService.ts | 12 ++++++------ .../configuration/common/jsonEditingService.ts | 2 +- .../keybinding/common/keybindingEditing.ts | 2 +- .../electron-sandbox/workingCopyBackupTracker.ts | 14 +++++++------- .../browser/abstractWorkspaceEditingService.ts | 2 +- 15 files changed, 40 insertions(+), 40 deletions(-) diff --git a/extensions/configuration-editing/src/settingsDocumentHelper.ts b/extensions/configuration-editing/src/settingsDocumentHelper.ts index 3a469148da6..e6afeb22d1d 100644 --- a/extensions/configuration-editing/src/settingsDocumentHelper.ts +++ b/extensions/configuration-editing/src/settingsDocumentHelper.ts @@ -83,7 +83,7 @@ export class SettingsDocument { completions.push(this.newSimpleCompletionItem('${folderPath}', range, localize('folderPath', "file path of the workspace folder the file is contained in (e.g. /Users/Development/myFolder)"))); completions.push(this.newSimpleCompletionItem('${appName}', range, localize('appName', "e.g. VS Code"))); completions.push(this.newSimpleCompletionItem('${remoteName}', range, localize('remoteName', "e.g. SSH"))); - completions.push(this.newSimpleCompletionItem('${dirty}', range, localize('dirty', "a dirty indicator if the active editor is dirty"))); + completions.push(this.newSimpleCompletionItem('${dirty}', range, localize('dirty', "an indicator for when the active editor has unsaved changes"))); completions.push(this.newSimpleCompletionItem('${separator}', range, localize('separator', "a conditional separator (' - ') that only shows when surrounded by variables with values"))); return Promise.resolve(completions); diff --git a/src/vs/workbench/browser/contextkeys.ts b/src/vs/workbench/browser/contextkeys.ts index 2890618f4d2..a6b1368a61d 100644 --- a/src/vs/workbench/browser/contextkeys.ts +++ b/src/vs/workbench/browser/contextkeys.ts @@ -33,7 +33,7 @@ export const OpenFolderWorkspaceSupportContext = new RawContextKey('ope export const EnterMultiRootWorkspaceSupportContext = new RawContextKey('enterMultiRootWorkspaceSupport', true, true); export const EmptyWorkspaceSupportContext = new RawContextKey('emptyWorkspaceSupport', true, true); -export const DirtyWorkingCopiesContext = new RawContextKey('dirtyWorkingCopies', false, localize('dirtyWorkingCopies', "Whether there are any dirty working copies")); +export const DirtyWorkingCopiesContext = new RawContextKey('dirtyWorkingCopies', false, localize('dirtyWorkingCopies', "Whether there are any working copies with unsaved changes")); export const RemoteNameContext = new RawContextKey('remoteName', '', localize('remoteName', "The name of the remote the window is connected to or an empty string if not connected to any remote")); export const VirtualWorkspaceContext = new RawContextKey('virtualWorkspace', '', localize('virtualWorkspace', "The scheme of the current workspace if is from a virtual file system or an empty string.")); diff --git a/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts b/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts index b421e11c532..896407ca8bd 100644 --- a/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts +++ b/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts @@ -149,11 +149,11 @@ export abstract class BaseEditorQuickAccessProvider extends PickerQuickAccessPro ariaLabel: (() => { if (mapGroupIdToGroupAriaLabel.size > 1) { return isDirty ? - localize('entryAriaLabelWithGroupDirty', "{0}, dirty, {1}", nameAndDescription, mapGroupIdToGroupAriaLabel.get(groupId)) : + localize('entryAriaLabelWithGroupDirty', "{0}, unsaved changes, {1}", nameAndDescription, mapGroupIdToGroupAriaLabel.get(groupId)) : localize('entryAriaLabelWithGroup', "{0}, {1}", nameAndDescription, mapGroupIdToGroupAriaLabel.get(groupId)); } - return isDirty ? localize('entryAriaLabelDirty', "{0}, dirty", nameAndDescription) : nameAndDescription; + return isDirty ? localize('entryAriaLabelDirty', "{0}, unsaved changes", nameAndDescription) : nameAndDescription; })(), description, iconClasses: getIconClasses(this.modelService, this.modeService, resource).concat(editor.getLabelExtraClasses()), diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index c592370424c..b8a8c87d94e 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -47,7 +47,7 @@ const registry = Registry.as(ConfigurationExtensions.Con }, 'workbench.editor.highlightModifiedTabs': { 'type': 'boolean', - 'markdownDescription': localize('highlightModifiedTabs', "Controls whether a top border is drawn on modified (dirty) editor tabs or not. This value is ignored when `#workbench.editor.showTabs#` is disabled."), + 'markdownDescription': localize('highlightModifiedTabs', "Controls whether a top border is drawn on tabs for editors that have unsaved changes. This value is ignored when `#workbench.editor.showTabs#` is disabled."), 'default': false }, 'workbench.editor.decorations.badges': { @@ -169,7 +169,7 @@ const registry = Registry.as(ConfigurationExtensions.Con }, 'workbench.editor.closeOnFileDelete': { 'type': 'boolean', - 'description': localize('closeOnFileDelete', "Controls whether editors showing a file that was opened during the session should close automatically when getting deleted or renamed by some other process. Disabling this will keep the editor open on such an event. Note that deleting from within the application will always close the editor and that dirty files will never close to preserve your data."), + 'description': localize('closeOnFileDelete', "Controls whether editors showing a file that was opened during the session should close automatically when getting deleted or renamed by some other process. Disabling this will keep the editor open on such an event. Note that deleting from within the application will always close the editor and that editors with unsaved changes will never close to preserve your data."), 'default': false }, 'workbench.editor.openPositioning': { @@ -228,7 +228,7 @@ const registry = Registry.as(ConfigurationExtensions.Con 'workbench.editor.limit.enabled': { 'type': 'boolean', 'default': false, - 'description': localize('limitEditorsEnablement', "Controls if the number of opened editors should be limited or not. When enabled, less recently used editors that are not dirty will close to make space for newly opening editors.") + 'description': localize('limitEditorsEnablement', "Controls if the number of opened editors should be limited or not. When enabled, less recently used editors will close to make space for newly opening editors.") }, 'workbench.editor.limit.value': { 'type': 'number', @@ -381,7 +381,7 @@ const registry = Registry.as(ConfigurationExtensions.Con localize('rootPath', "`${rootPath}`: file path of the opened workspace or folder (e.g. /Users/Development/myWorkspace)."), localize('appName', "`${appName}`: e.g. VS Code."), localize('remoteName', "`${remoteName}`: e.g. SSH"), - localize('dirty', "`${dirty}`: a dirty indicator if the active editor is dirty."), + localize('dirty', "`${dirty}`: an indicator for when the active editor has unsaved changes."), localize('separator', "`${separator}`: a conditional separator (\" - \") that only shows when surrounded by variables with values or static text.") ].join('\n- '); // intentionally concatenated to not produce a string that is too long for translations diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index a12258f9297..d363466b598 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -38,7 +38,7 @@ export const DEFAULT_EDITOR_ASSOCIATION = { }; // Editor State Context Keys -export const ActiveEditorDirtyContext = new RawContextKey('activeEditorIsDirty', false, localize('activeEditorIsDirty', "Whether the active editor is dirty")); +export const ActiveEditorDirtyContext = new RawContextKey('activeEditorIsDirty', false, localize('activeEditorIsDirty', "Whether the active editor has unsaved changes")); export const ActiveEditorPinnedContext = new RawContextKey('activeEditorIsNotPreview', false, localize('activeEditorIsNotPreview', "Whether the active editor is not in preview mode")); export const ActiveEditorStickyContext = new RawContextKey('activeEditorIsPinned', false, localize('activeEditorIsPinned', "Whether the active editor is pinned")); export const ActiveEditorReadonlyContext = new RawContextKey('activeEditorIsReadonly', false, localize('activeEditorIsReadonly', "Whether the active editor is readonly")); diff --git a/src/vs/workbench/common/theme.ts b/src/vs/workbench/common/theme.ts index c0ff64417db..0032fb45349 100644 --- a/src/vs/workbench/common/theme.ts +++ b/src/vs/workbench/common/theme.ts @@ -165,25 +165,25 @@ export const TAB_ACTIVE_MODIFIED_BORDER = registerColor('tab.activeModifiedBorde dark: '#3399CC', light: '#33AAEE', hc: null -}, localize('tabActiveModifiedBorder', "Border on the top of modified (dirty) active tabs in an active group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +}, localize('tabActiveModifiedBorder', "Border on the top of modified active tabs in an active group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); export const TAB_INACTIVE_MODIFIED_BORDER = registerColor('tab.inactiveModifiedBorder', { dark: transparent(TAB_ACTIVE_MODIFIED_BORDER, 0.5), light: transparent(TAB_ACTIVE_MODIFIED_BORDER, 0.5), hc: Color.white -}, localize('tabInactiveModifiedBorder', "Border on the top of modified (dirty) inactive tabs in an active group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +}, localize('tabInactiveModifiedBorder', "Border on the top of modified inactive tabs in an active group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); export const TAB_UNFOCUSED_ACTIVE_MODIFIED_BORDER = registerColor('tab.unfocusedActiveModifiedBorder', { dark: transparent(TAB_ACTIVE_MODIFIED_BORDER, 0.5), light: transparent(TAB_ACTIVE_MODIFIED_BORDER, 0.7), hc: Color.white -}, localize('unfocusedActiveModifiedBorder', "Border on the top of modified (dirty) active tabs in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +}, localize('unfocusedActiveModifiedBorder', "Border on the top of modified active tabs in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); export const TAB_UNFOCUSED_INACTIVE_MODIFIED_BORDER = registerColor('tab.unfocusedInactiveModifiedBorder', { dark: transparent(TAB_INACTIVE_MODIFIED_BORDER, 0.5), light: transparent(TAB_INACTIVE_MODIFIED_BORDER, 0.5), hc: Color.white -}, localize('unfocusedINactiveModifiedBorder', "Border on the top of modified (dirty) inactive tabs in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +}, localize('unfocusedINactiveModifiedBorder', "Border on the top of modified inactive tabs in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); //#endregion diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index 07e83ac0ddc..8c3bef3516c 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -113,7 +113,7 @@ const hotExitConfiguration: IConfigurationPropertySchema = isNative ? 'enum': [HotExitConfiguration.OFF, HotExitConfiguration.ON_EXIT, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE], 'default': HotExitConfiguration.ON_EXIT, 'markdownEnumDescriptions': [ - nls.localize('hotExit.off', 'Disable hot exit. A prompt will show when attempting to close a window with dirty files.'), + nls.localize('hotExit.off', 'Disable hot exit. A prompt will show when attempting to close a window with editors that have unsaved changes.'), nls.localize('hotExit.onExit', 'Hot exit will be triggered when the last window is closed on Windows/Linux or when the `workbench.action.quit` command is triggered (command palette, keybinding, menu). All windows without folders opened will be restored upon next launch. A list of previously opened windows with unsaved files can be accessed via `File > Open Recent > More...`'), nls.localize('hotExit.onExitAndWindowClose', 'Hot exit will be triggered when the last window is closed on Windows/Linux or when the `workbench.action.quit` command is triggered (command palette, keybinding, menu), and also for any window with a folder opened regardless of whether it\'s the last window. All windows without folders opened will be restored upon next launch. A list of previously opened windows with unsaved files can be accessed via `File > Open Recent > More...`') ], @@ -124,7 +124,7 @@ const hotExitConfiguration: IConfigurationPropertySchema = isNative ? 'enum': [HotExitConfiguration.OFF, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE], 'default': HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, 'markdownEnumDescriptions': [ - nls.localize('hotExit.off', 'Disable hot exit. A prompt will show when attempting to close a window with dirty files.'), + nls.localize('hotExit.off', 'Disable hot exit. A prompt will show when attempting to close a window with editors that have unsaved changes.'), nls.localize('hotExit.onExitAndWindowCloseBrowser', 'Hot exit will be triggered when the browser quits or the window or tab is closed.') ], 'description': nls.localize('hotExit', "Controls whether unsaved files are remembered between sessions, allowing the save prompt when exiting the editor to be skipped.", HotExitConfiguration.ON_EXIT, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) @@ -229,18 +229,18 @@ configurationRegistry.registerConfiguration({ 'type': 'string', 'enum': [AutoSaveConfiguration.OFF, AutoSaveConfiguration.AFTER_DELAY, AutoSaveConfiguration.ON_FOCUS_CHANGE, AutoSaveConfiguration.ON_WINDOW_CHANGE], 'markdownEnumDescriptions': [ - nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'files.autoSave.off' }, "A dirty editor is never automatically saved."), - nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'files.autoSave.afterDelay' }, "A dirty editor is automatically saved after the configured `#files.autoSaveDelay#`."), - nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'files.autoSave.onFocusChange' }, "A dirty editor is automatically saved when the editor loses focus."), - nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'files.autoSave.onWindowChange' }, "A dirty editor is automatically saved when the window loses focus.") + nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'files.autoSave.off' }, "An editor with changes is never automatically saved."), + nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'files.autoSave.afterDelay' }, "An editor with changes is automatically saved after the configured `#files.autoSaveDelay#`."), + nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'files.autoSave.onFocusChange' }, "An editor with changes is automatically saved when the editor loses focus."), + nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'files.autoSave.onWindowChange' }, "An editor with changes is automatically saved when the window loses focus.") ], 'default': isWeb ? AutoSaveConfiguration.AFTER_DELAY : AutoSaveConfiguration.OFF, - 'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'autoSave' }, "Controls auto save of dirty editors. Read more about autosave [here](https://code.visualstudio.com/docs/editor/codebasics#_save-auto-save).", AutoSaveConfiguration.OFF, AutoSaveConfiguration.AFTER_DELAY, AutoSaveConfiguration.ON_FOCUS_CHANGE, AutoSaveConfiguration.ON_WINDOW_CHANGE, AutoSaveConfiguration.AFTER_DELAY) + 'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'autoSave' }, "Controls auto save of editors that have unsaved changes. Read more about autosave [here](https://code.visualstudio.com/docs/editor/codebasics#_save-auto-save).", AutoSaveConfiguration.OFF, AutoSaveConfiguration.AFTER_DELAY, AutoSaveConfiguration.ON_FOCUS_CHANGE, AutoSaveConfiguration.ON_WINDOW_CHANGE, AutoSaveConfiguration.AFTER_DELAY) }, 'files.autoSaveDelay': { 'type': 'number', 'default': 1000, - 'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'autoSaveDelay' }, "Controls the delay in ms after which a dirty editor is saved automatically. Only applies when `#files.autoSave#` is set to `{0}`.", AutoSaveConfiguration.AFTER_DELAY) + 'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'autoSaveDelay' }, "Controls the delay in milliseconds after which an editor with unsaved changes is saved automatically. Only applies when `#files.autoSave#` is set to `{0}`.", AutoSaveConfiguration.AFTER_DELAY) }, 'files.watcherExclude': { 'type': 'object', diff --git a/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts b/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts index 83031ac57b8..b242e3d36ac 100644 --- a/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts @@ -894,7 +894,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider { diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index 848903b8a3f..fe945b86aad 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -312,7 +312,7 @@ const terminalConfiguration: IConfigurationNode = { default: 'never' }, [TerminalSettingId.ConfirmOnKill]: { - description: localize('terminal.integrated.confirmOnKill', "Controls whether to confirm killing terminals when they have child processes. When set to editor, terminals in the editor area will be marked as dirty when they have child processes. Note that child process detection may not work well for shells like Git Bash which don't run their processes as child processes of the shell."), + description: localize('terminal.integrated.confirmOnKill', "Controls whether to confirm killing terminals when they have child processes. When set to editor, terminals in the editor area will be marked as changed when they have child processes. Note that child process detection may not work well for shells like Git Bash which don't run their processes as child processes of the shell."), type: 'string', enum: ['never', 'editor', 'panel', 'always'], enumDescriptions: [ diff --git a/src/vs/workbench/electron-sandbox/actions/windowActions.ts b/src/vs/workbench/electron-sandbox/actions/windowActions.ts index 95624815e8b..a7962686b93 100644 --- a/src/vs/workbench/electron-sandbox/actions/windowActions.ts +++ b/src/vs/workbench/electron-sandbox/actions/windowActions.ts @@ -214,7 +214,7 @@ abstract class BaseSwitchWindow extends Action2 { return { payload: window.id, label: window.title, - ariaLabel: window.dirty ? localize('windowDirtyAriaLabel', "{0}, dirty window", window.title) : window.title, + ariaLabel: window.dirty ? localize('windowDirtyAriaLabel', "{0}, window with unsaved changes", window.title) : window.title, iconClasses: getIconClasses(modelService, modeService, resource, fileKind), description: (currentWindowId === window.id) ? localize('current', "Current Window") : undefined, buttons: currentWindowId !== window.id ? window.dirty ? [this.closeDirtyWindowAction] : [this.closeWindowAction] : undefined diff --git a/src/vs/workbench/services/configuration/common/configurationEditingService.ts b/src/vs/workbench/services/configuration/common/configurationEditingService.ts index 3c26c0f94ac..ff8f5e35abb 100644 --- a/src/vs/workbench/services/configuration/common/configurationEditingService.ts +++ b/src/vs/workbench/services/configuration/common/configurationEditingService.ts @@ -387,18 +387,18 @@ export class ConfigurationEditingService { } case ConfigurationEditingErrorCode.ERROR_CONFIGURATION_FILE_DIRTY: { if (operation.workspaceStandAloneConfigurationKey === TASKS_CONFIGURATION_KEY) { - return nls.localize('errorTasksConfigurationFileDirty', "Unable to write into tasks configuration file because the file is dirty. Please save it first and then try again."); + return nls.localize('errorTasksConfigurationFileDirty', "Unable to write into tasks configuration file because the file has unsaved changes. Please save it first and then try again."); } if (operation.workspaceStandAloneConfigurationKey === LAUNCH_CONFIGURATION_KEY) { - return nls.localize('errorLaunchConfigurationFileDirty', "Unable to write into launch configuration file because the file is dirty. Please save it first and then try again."); + return nls.localize('errorLaunchConfigurationFileDirty', "Unable to write into launch configuration file because the file has unsaved changes. Please save it first and then try again."); } switch (target) { case EditableConfigurationTarget.USER_LOCAL: - return nls.localize('errorConfigurationFileDirty', "Unable to write into user settings because the file is dirty. Please save the user settings file first and then try again."); + return nls.localize('errorConfigurationFileDirty', "Unable to write into user settings because the file has unsaved changes. Please save the user settings file first and then try again."); case EditableConfigurationTarget.USER_REMOTE: - return nls.localize('errorRemoteConfigurationFileDirty', "Unable to write into remote user settings because the file is dirty. Please save the remote user settings file first and then try again."); + return nls.localize('errorRemoteConfigurationFileDirty', "Unable to write into remote user settings because the file has unsaved changes. Please save the remote user settings file first and then try again."); case EditableConfigurationTarget.WORKSPACE: - return nls.localize('errorConfigurationFileDirtyWorkspace', "Unable to write into workspace settings because the file is dirty. Please save the workspace settings file first and then try again."); + return nls.localize('errorConfigurationFileDirtyWorkspace', "Unable to write into workspace settings because the file has unsaved changes. Please save the workspace settings file first and then try again."); case EditableConfigurationTarget.WORKSPACE_FOLDER: let workspaceFolderName: string = '<>'; if (operation.resource) { @@ -407,7 +407,7 @@ export class ConfigurationEditingService { workspaceFolderName = folder.name; } } - return nls.localize('errorConfigurationFileDirtyFolder', "Unable to write into folder settings because the file is dirty. Please save the '{0}' folder settings file first and then try again.", workspaceFolderName); + return nls.localize('errorConfigurationFileDirtyFolder', "Unable to write into folder settings because the file has unsaved changes. Please save the '{0}' folder settings file first and then try again.", workspaceFolderName); default: return ''; } diff --git a/src/vs/workbench/services/configuration/common/jsonEditingService.ts b/src/vs/workbench/services/configuration/common/jsonEditingService.ts index a9686fae2d9..cd00727f8d1 100644 --- a/src/vs/workbench/services/configuration/common/jsonEditingService.ts +++ b/src/vs/workbench/services/configuration/common/jsonEditingService.ts @@ -134,7 +134,7 @@ export class JSONEditingService implements IJSONEditingService { return nls.localize('errorInvalidFile', "Unable to write into the file. Please open the file to correct errors/warnings in the file and try again."); } case JSONEditingErrorCode.ERROR_FILE_DIRTY: { - return nls.localize('errorFileDirty', "Unable to write into the file because the file is dirty. Please save the file and try again."); + return nls.localize('errorFileDirty', "Unable to write into the file because the file has unsaved changes. Please save the file and try again."); } } } diff --git a/src/vs/workbench/services/keybinding/common/keybindingEditing.ts b/src/vs/workbench/services/keybinding/common/keybindingEditing.ts index 3d25314fdcd..9935d106589 100644 --- a/src/vs/workbench/services/keybinding/common/keybindingEditing.ts +++ b/src/vs/workbench/services/keybinding/common/keybindingEditing.ts @@ -256,7 +256,7 @@ export class KeybindingsEditingService extends Disposable implements IKeybinding // Target cannot be dirty if not writing into buffer if (this.textFileService.isDirty(this.resource)) { - return Promise.reject(new Error(localize('errorKeybindingsFileDirty', "Unable to write because the keybindings configuration file is dirty. Please save it first and then try again."))); + return Promise.reject(new Error(localize('errorKeybindingsFileDirty', "Unable to write because the keybindings configuration file has unsaved changes. Please save it first and then try again."))); } return this.resolveModelReference() diff --git a/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker.ts b/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker.ts index 2687aaf4434..fb7cd5adc95 100644 --- a/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker.ts +++ b/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker.ts @@ -115,7 +115,7 @@ export class NativeWorkingCopyBackupTracker extends WorkingCopyBackupTracker imp return false; // do not block shutdown during extension development (https://github.com/microsoft/vscode/issues/115028) } - this.showErrorDialog(localize('backupTrackerBackupFailed', "The following dirty editors could not be saved to the back up location."), remainingDirtyWorkingCopies, backupError); + this.showErrorDialog(localize('backupTrackerBackupFailed', "The following editors with unsaved changes could not be saved to the back up location."), remainingDirtyWorkingCopies, backupError); return true; // veto (the backup failed) } @@ -131,7 +131,7 @@ export class NativeWorkingCopyBackupTracker extends WorkingCopyBackupTracker imp return false; // do not block shutdown during extension development (https://github.com/microsoft/vscode/issues/115028) } - this.showErrorDialog(localize('backupTrackerConfirmFailed', "The following dirty editors could not be saved or reverted."), remainingDirtyWorkingCopies, error); + this.showErrorDialog(localize('backupTrackerConfirmFailed', "The following editors with unsaved changes could not be saved or reverted."), remainingDirtyWorkingCopies, error); return true; // veto (save or revert failed) } @@ -140,7 +140,7 @@ export class NativeWorkingCopyBackupTracker extends WorkingCopyBackupTracker imp private showErrorDialog(msg: string, workingCopies: readonly IWorkingCopy[], error?: Error): void { const dirtyWorkingCopies = workingCopies.filter(workingCopy => workingCopy.isDirty()); - const advice = localize('backupErrorDetails', "Try saving or reverting the dirty editors first and then try again."); + const advice = localize('backupErrorDetails', "Try saving or reverting the editors with unsaved changes first and then try again."); const detail = dirtyWorkingCopies.length ? getFileNamesMessage(dirtyWorkingCopies.map(x => x.name)) + '\n' + advice : advice; @@ -225,8 +225,8 @@ export class NativeWorkingCopyBackupTracker extends WorkingCopyBackupTracker imp error = backupError; } }, - localize('backupBeforeShutdownMessage', "Backing up dirty editors is taking longer than expected..."), - localize('backupBeforeShutdownDetail', "Click 'Cancel' to stop waiting and to save or revert dirty editors.") + localize('backupBeforeShutdownMessage', "Backing up editors with unsaved changes is taking longer than expected..."), + localize('backupBeforeShutdownDetail', "Click 'Cancel' to stop waiting and to save or revert editors with unsaved changes.") ); return { backups, error }; @@ -296,7 +296,7 @@ export class NativeWorkingCopyBackupTracker extends WorkingCopyBackupTracker imp if (result !== false) { await Promises.settled(dirtyWorkingCopies.map(workingCopy => workingCopy.isDirty() ? workingCopy.save(saveOptions) : Promise.resolve(true))); } - }, localize('saveBeforeShutdown', "Saving dirty editors is taking longer than expected...")); + }, localize('saveBeforeShutdown', "Saving editors with unsaved changes is taking longer than expected...")); } private doRevertAllBeforeShutdown(dirtyWorkingCopies: IWorkingCopy[]): Promise { @@ -313,7 +313,7 @@ export class NativeWorkingCopyBackupTracker extends WorkingCopyBackupTracker imp // If we still have dirty working copies, revert those directly // unless the revert operation was not successful (e.g. cancelled) await Promises.settled(dirtyWorkingCopies.map(workingCopy => workingCopy.isDirty() ? workingCopy.revert(revertOptions) : Promise.resolve())); - }, localize('revertBeforeShutdown', "Reverting dirty editors is taking longer than expected...")); + }, localize('revertBeforeShutdown', "Reverting editors with unsaved changes is taking longer than expected...")); } private withProgressAndCancellation(promiseFactory: (token: CancellationToken) => Promise, title: string, detail?: string): Promise { diff --git a/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts b/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts index a8f715a7048..5c30da7a14d 100644 --- a/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts +++ b/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts @@ -307,7 +307,7 @@ export abstract class AbstractWorkspaceEditingService implements IWorkspaceEditi } private onWorkspaceConfigurationFileDirtyError(): void { - const message = localize('errorWorkspaceConfigurationFileDirty', "Unable to write into workspace configuration file because the file is dirty. Please save it and try again."); + const message = localize('errorWorkspaceConfigurationFileDirty', "Unable to write into workspace configuration file because the file has unsaved changes. Please save it and try again."); this.askToOpenWorkspaceConfigurationFile(message); } From 51f636f90c112e975625d95657600a6bc6eafece Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 25 Oct 2021 15:31:58 +0200 Subject: [PATCH 23/24] #134909 queue writes and fix tests --- .../configuration/common/userConfigurationFileService.ts | 7 +++++-- .../userDataSync/test/common/userDataSyncClient.ts | 2 ++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/configuration/common/userConfigurationFileService.ts b/src/vs/platform/configuration/common/userConfigurationFileService.ts index 12245ff5e55..2507cb3e1f4 100644 --- a/src/vs/platform/configuration/common/userConfigurationFileService.ts +++ b/src/vs/platform/configuration/common/userConfigurationFileService.ts @@ -66,7 +66,7 @@ export class UserConfigurationFileService implements IUserConfigurationFileServi if (edit) { content = content.substring(0, edit.offset) + edit.content + content.substring(edit.offset + edit.length); try { - await this.write(VSBuffer.fromString(content), { etag, mtime }); + await this.fileService.writeFile(this.environmentService.settingsResource, VSBuffer.fromString(content), { etag, mtime }); } catch (error) { if ((error).fileOperationResult === FileOperationResult.FILE_MODIFIED_SINCE) { throw new Error(UserConfigurationErrorCode.ERROR_FILE_MODIFIED_SINCE); @@ -76,7 +76,10 @@ export class UserConfigurationFileService implements IUserConfigurationFileServi } async write(content: VSBuffer, options?: IWriteFileOptions): Promise { - await this.fileService.writeFile(this.environmentService.settingsResource, content, options); + // queue up writes to prevent race conditions + return this.queue.queue(async () => { + await this.fileService.writeFile(this.environmentService.settingsResource, content, options); + }); } private getEdits({ value, path }: IJSONValue, modelContent: string, formattingOptions: FormattingOptions): Edit[] { diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts index 99fb6c02c0c..cf3e6333d88 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts @@ -16,6 +16,7 @@ import { generateUuid } from 'vs/base/common/uuid'; import { IHeaders, IRequestContext, IRequestOptions } from 'vs/base/parts/request/common/request'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ConfigurationService } from 'vs/platform/configuration/common/configurationService'; +import { IUserConfigurationFileService, UserConfigurationFileService } from 'vs/platform/configuration/common/userConfigurationFileService'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { GlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService'; import { DidUninstallExtensionEvent, IExtensionGalleryService, IExtensionManagementService, IGlobalExtensionEnablementService, InstallExtensionResult } from 'vs/platform/extensionManagement/common/extensionManagement'; @@ -88,6 +89,7 @@ export class UserDataSyncClient extends Disposable { const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService)); await configurationService.initialize(); this.instantiationService.stub(IConfigurationService, configurationService); + this.instantiationService.stub(IUserConfigurationFileService, this.instantiationService.createInstance(UserConfigurationFileService)); this.instantiationService.stub(IRequestService, this.testServer); From c208d9b0c83141480a62f067b833ca63beb7b723 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 25 Oct 2021 15:54:54 +0200 Subject: [PATCH 24/24] Fix wording in test --- .../services/keybinding/test/browser/keybindingEditing.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/services/keybinding/test/browser/keybindingEditing.test.ts b/src/vs/workbench/services/keybinding/test/browser/keybindingEditing.test.ts index cd0d99dc3ba..196876fd1fe 100644 --- a/src/vs/workbench/services/keybinding/test/browser/keybindingEditing.test.ts +++ b/src/vs/workbench/services/keybinding/test/browser/keybindingEditing.test.ts @@ -152,7 +152,7 @@ suite('KeybindingsEditing', () => { instantiationService.stub(ITextFileService, 'isDirty', true); return testObject.editKeybinding(aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape } }), 'alt+c', undefined) .then(() => assert.fail('Should fail with dirty error'), - error => assert.strictEqual(error.message, 'Unable to write because the keybindings configuration file is dirty. Please save it first and then try again.')); + error => assert.strictEqual(error.message, 'Unable to write because the keybindings configuration file has unsaved changes. Please save it first and then try again.')); }); test('errors cases - did not find an array', async () => {