From e97a41e850c0b03564e237a713c5e08b1822f762 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Thu, 7 Nov 2019 18:27:47 +0800 Subject: [PATCH 001/255] fix Chinese system input in iOS 13.2 resolve https://github.com/microsoft/monaco-editor/issues/1663 --- src/vs/editor/browser/controller/textAreaInput.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/browser/controller/textAreaInput.ts b/src/vs/editor/browser/controller/textAreaInput.ts index daecfbc4f68..2e64c93ba59 100644 --- a/src/vs/editor/browser/controller/textAreaInput.ts +++ b/src/vs/editor/browser/controller/textAreaInput.ts @@ -274,7 +274,11 @@ export class TextAreaInput extends Disposable { this._register(dom.addDisposableListener(textArea.domNode, 'compositionend', (e: CompositionEvent) => { this._lastTextAreaEvent = TextAreaInputEventType.compositionend; - + // https://github.com/microsoft/monaco-editor/issues/1663 + // On iOS 13.2, Chinese system IME randomly trigger an additional compositionend event with empty data + if (!this._isDoingComposition) { + return; + } if (compositionDataInValid(e.locale)) { // https://github.com/Microsoft/monaco-editor/issues/339 const [newState, typeInput] = deduceInputFromTextAreaValue(/*couldBeEmojiInput*/false, /*couldBeTypingAtOffset0*/false); From 14208840271c9f764ac759db4d412365e7ab31ba Mon Sep 17 00:00:00 2001 From: grey275 Date: Mon, 18 Nov 2019 14:15:06 -0800 Subject: [PATCH 002/255] add language-specific overrides for breadcrumb settings --- .../browser/parts/editor/breadcrumbs.ts | 26 +++++++++++++++++++ .../browser/parts/editor/breadcrumbsModel.ts | 22 +++++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbs.ts b/src/vs/workbench/browser/parts/editor/breadcrumbs.ts index ac600063f15..a62a5f543b0 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbs.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbs.ts @@ -170,131 +170,157 @@ Registry.as(Extensions.Configuration).registerConfigurat 'breadcrumbs.showFiles': { type: 'boolean', default: true, + overridable: true, markdownDescription: localize('filteredTypes.file', "When enabled breadcrumbs show `file`-symbols.") }, 'breadcrumbs.showModules': { type: 'boolean', default: true, + overridable: true, markdownDescription: localize('filteredTypes.module', "When enabled breadcrumbs show `module`-symbols.") }, 'breadcrumbs.showNamespaces': { type: 'boolean', default: true, + overridable: true, markdownDescription: localize('filteredTypes.namespace', "When enabled breadcrumbs show `namespace`-symbols.") }, 'breadcrumbs.showPackages': { type: 'boolean', default: true, + overridable: true, markdownDescription: localize('filteredTypes.package', "When enabled breadcrumbs show `package`-symbols.") }, 'breadcrumbs.showClasses': { type: 'boolean', default: true, + overridable: true, markdownDescription: localize('filteredTypes.class', "When enabled breadcrumbs show `class`-symbols.") }, 'breadcrumbs.showMethods': { type: 'boolean', default: true, + overridable: true, markdownDescription: localize('filteredTypes.method', "When enabled breadcrumbs show `method`-symbols.") }, 'breadcrumbs.showProperties': { type: 'boolean', default: true, + overridable: true, markdownDescription: localize('filteredTypes.property', "When enabled breadcrumbs show `property`-symbols.") }, 'breadcrumbs.showFields': { type: 'boolean', default: true, + overridable: true, markdownDescription: localize('filteredTypes.field', "When enabled breadcrumbs show `field`-symbols.") }, 'breadcrumbs.showConstructors': { type: 'boolean', default: true, + overridable: true, markdownDescription: localize('filteredTypes.constructor', "When enabled breadcrumbs show `constructor`-symbols.") }, 'breadcrumbs.showEnums': { type: 'boolean', default: true, + overridable: true, markdownDescription: localize('filteredTypes.enum', "When enabled breadcrumbs show `enum`-symbols.") }, 'breadcrumbs.showInterfaces': { type: 'boolean', default: true, + overridable: true, markdownDescription: localize('filteredTypes.interface', "When enabled breadcrumbs show `interface`-symbols.") }, 'breadcrumbs.showFunctions': { type: 'boolean', default: true, + overridable: true, markdownDescription: localize('filteredTypes.function', "When enabled breadcrumbs show `function`-symbols.") }, 'breadcrumbs.showVariables': { type: 'boolean', default: true, + overridable: true, markdownDescription: localize('filteredTypes.variable', "When enabled breadcrumbs show `variable`-symbols.") }, 'breadcrumbs.showConstants': { type: 'boolean', default: true, + overridable: true, markdownDescription: localize('filteredTypes.constant', "When enabled breadcrumbs show `constant`-symbols.") }, 'breadcrumbs.showStrings': { type: 'boolean', default: true, + overridable: true, markdownDescription: localize('filteredTypes.string', "When enabled breadcrumbs show `string`-symbols.") }, 'breadcrumbs.showNumbers': { type: 'boolean', default: true, + overridable: true, markdownDescription: localize('filteredTypes.number', "When enabled breadcrumbs show `number`-symbols.") }, 'breadcrumbs.showBooleans': { type: 'boolean', default: true, + overridable: true, markdownDescription: localize('filteredTypes.boolean', "When enabled breadcrumbs show `boolean`-symbols.") }, 'breadcrumbs.showArrays': { type: 'boolean', default: true, + overridable: true, markdownDescription: localize('filteredTypes.array', "When enabled breadcrumbs show `array`-symbols.") }, 'breadcrumbs.showObjects': { type: 'boolean', default: true, + overridable: true, markdownDescription: localize('filteredTypes.object', "When enabled breadcrumbs show `object`-symbols.") }, 'breadcrumbs.showKeys': { type: 'boolean', default: true, + overridable: true, markdownDescription: localize('filteredTypes.key', "When enabled breadcrumbs show `key`-symbols.") }, 'breadcrumbs.showNull': { type: 'boolean', default: true, + overridable: true, markdownDescription: localize('filteredTypes.null', "When enabled breadcrumbs show `null`-symbols.") }, 'breadcrumbs.showEnumMembers': { type: 'boolean', default: true, + overridable: true, markdownDescription: localize('filteredTypes.enumMember', "When enabled breadcrumbs show `enumMember`-symbols.") }, 'breadcrumbs.showStructs': { type: 'boolean', default: true, + overridable: true, markdownDescription: localize('filteredTypes.struct', "When enabled breadcrumbs show `struct`-symbols.") }, 'breadcrumbs.showEvents': { type: 'boolean', default: true, + overridable: true, markdownDescription: localize('filteredTypes.event', "When enabled breadcrumbs show `event`-symbols.") }, 'breadcrumbs.showOperators': { type: 'boolean', default: true, + overridable: true, markdownDescription: localize('filteredTypes.operator', "When enabled breadcrumbs show `operator`-symbols.") }, 'breadcrumbs.showTypeParameters': { type: 'boolean', default: true, + overridable: true, markdownDescription: localize('filteredTypes.typeParameter', "When enabled breadcrumbs show `typeParameter`-symbols.") } } diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts index f05157c5257..a73a3442a78 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts @@ -23,6 +23,7 @@ import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs import { FileKind } from 'vs/platform/files/common/files'; import { withNullAsUndefined } from 'vs/base/common/types'; import { OutlineFilter } from 'vs/editor/contrib/documentSymbols/outlineTree'; +import { ITextModel } from 'vs/editor/common/model'; export class FileElement { constructor( @@ -142,6 +143,16 @@ export class EditorBreadcrumbsModel { this._disposables.add(this._configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('breadcrumbs')) { this._updateOutline(true); + return; + } + if (this._editor && this._editor.getModel()) { + const editorModel = this._editor.getModel() as ITextModel; + const languageName = editorModel.getLanguageIdentifier().language; + + // checking for changes in the current language override config + if (e.affectsConfiguration(`[${languageName}]`)) { + this._updateOutline(true); + } } })); @@ -250,7 +261,16 @@ export class EditorBreadcrumbsModel { private _isFiltered(element: TreeElement): boolean { if (element instanceof OutlineElement) { const key = `breadcrumbs.${OutlineFilter.kindToConfigName[element.symbol.kind]}`; - return !this._configurationService.getValue(key); + + // If possible, look for language specific overrides when reading configuration. + if (this._editor && this._editor.getModel()) { + const model = this._editor.getModel() as ITextModel; + return !this._configurationService.getValue(key, { + overrideIdentifier: model.getLanguageIdentifier().language + }); + } + + return this._configurationService.getValue(key); } return false; } From 9d9a99d67562315c34c195865c0720abff96c721 Mon Sep 17 00:00:00 2001 From: grey275 Date: Tue, 19 Nov 2019 14:17:32 -0800 Subject: [PATCH 003/255] switch to using TextResourceConfigurationService to account for configuration overides when reading breadcrumb configurations involving filteredTypes --- .../browser/parts/editor/breadcrumbsControl.ts | 9 ++++++++- .../browser/parts/editor/breadcrumbsModel.ts | 13 +++++-------- .../browser/parts/editor/breadcrumbModel.test.ts | 4 ++-- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 7aab9754619..39782820673 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -47,6 +47,7 @@ import { IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; import { onDidChangeZoomLevel } from 'vs/base/browser/browser'; import { withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types'; import { ILabelService } from 'vs/platform/label/common/label'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; class Item extends BreadcrumbsItem { @@ -168,6 +169,7 @@ export class BreadcrumbsControl { @IThemeService private readonly _themeService: IThemeService, @IQuickOpenService private readonly _quickOpenService: IQuickOpenService, @IConfigurationService private readonly _configurationService: IConfigurationService, + @ITextResourceConfigurationService private readonly _textResourceConfigurationService: ITextResourceConfigurationService, @IFileService private readonly _fileService: IFileService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @ILabelService private readonly _labelService: ILabelService, @@ -246,7 +248,12 @@ export class BreadcrumbsControl { const uri = input.getResource()!; const editor = this._getActiveCodeEditor(); - const model = new EditorBreadcrumbsModel(uri, editor, this._configurationService, this._workspaceService); + const model = new EditorBreadcrumbsModel( + uri, editor, + this._configurationService, + this._textResourceConfigurationService, + this._workspaceService + ); dom.toggleClass(this.domNode, 'relative-path', model.isRelative()); dom.toggleClass(this.domNode, 'backslash-path', this._labelService.getSeparator(uri.scheme, uri.authority) === '\\'); diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts index a73a3442a78..4a9ee9ec117 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts @@ -24,6 +24,7 @@ import { FileKind } from 'vs/platform/files/common/files'; import { withNullAsUndefined } from 'vs/base/common/types'; import { OutlineFilter } from 'vs/editor/contrib/documentSymbols/outlineTree'; import { ITextModel } from 'vs/editor/common/model'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; export class FileElement { constructor( @@ -54,9 +55,9 @@ export class EditorBreadcrumbsModel { private readonly _uri: URI, private readonly _editor: ICodeEditor | undefined, @IConfigurationService private readonly _configurationService: IConfigurationService, + @ITextResourceConfigurationService private readonly _textResourceConfigurationService: ITextResourceConfigurationService, @IWorkspaceContextService workspaceService: IWorkspaceContextService, ) { - this._cfgFilePath = BreadcrumbsConfig.FilePath.bindTo(_configurationService); this._cfgSymbolPath = BreadcrumbsConfig.SymbolPath.bindTo(_configurationService); @@ -261,16 +262,12 @@ export class EditorBreadcrumbsModel { private _isFiltered(element: TreeElement): boolean { if (element instanceof OutlineElement) { const key = `breadcrumbs.${OutlineFilter.kindToConfigName[element.symbol.kind]}`; - - // If possible, look for language specific overrides when reading configuration. + let uri: URI | undefined; if (this._editor && this._editor.getModel()) { const model = this._editor.getModel() as ITextModel; - return !this._configurationService.getValue(key, { - overrideIdentifier: model.getLanguageIdentifier().language - }); + uri = model.uri; } - - return this._configurationService.getValue(key); + return !this._textResourceConfigurationService.getValue(uri, key); } return false; } diff --git a/src/vs/workbench/test/browser/parts/editor/breadcrumbModel.test.ts b/src/vs/workbench/test/browser/parts/editor/breadcrumbModel.test.ts index c26f9a10ecf..01af7282519 100644 --- a/src/vs/workbench/test/browser/parts/editor/breadcrumbModel.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/breadcrumbModel.test.ts @@ -29,7 +29,7 @@ suite('Breadcrumb Model', function () { test('only uri, inside workspace', function () { - let model = new EditorBreadcrumbsModel(URI.parse('foo:/bar/baz/ws/some/path/file.ts'), undefined, configService, workspaceService); + let model = new EditorBreadcrumbsModel(URI.parse('foo:/bar/baz/ws/some/path/file.ts'), undefined, configService, configService, workspaceService); let elements = model.getElements(); assert.equal(elements.length, 3); @@ -44,7 +44,7 @@ suite('Breadcrumb Model', function () { test('only uri, outside workspace', function () { - let model = new EditorBreadcrumbsModel(URI.parse('foo:/outside/file.ts'), undefined, configService, workspaceService); + let model = new EditorBreadcrumbsModel(URI.parse('foo:/outside/file.ts'), undefined, configService, configService, workspaceService); let elements = model.getElements(); assert.equal(elements.length, 2); From 25a1bbb9db54401b7dc6248f369bd412d9a18136 Mon Sep 17 00:00:00 2001 From: grey275 Date: Tue, 19 Nov 2019 14:30:23 -0800 Subject: [PATCH 004/255] implement language specific overrides for outline config options with corresponding filteredTypes --- .../contrib/documentSymbols/outlineTree.ts | 37 ++++++++++--------- .../outline/browser/outline.contribution.ts | 18 +++++++++ .../outline/browser/outlineNavigation.ts | 6 +-- .../contrib/outline/browser/outlinePanel.ts | 1 - 4 files changed, 40 insertions(+), 22 deletions(-) diff --git a/src/vs/editor/contrib/documentSymbols/outlineTree.ts b/src/vs/editor/contrib/documentSymbols/outlineTree.ts index 970453bc12b..298ba5ec5e1 100644 --- a/src/vs/editor/contrib/documentSymbols/outlineTree.ts +++ b/src/vs/editor/contrib/documentSymbols/outlineTree.ts @@ -7,7 +7,7 @@ import * as dom from 'vs/base/browser/dom'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { IIdentityProvider, IKeyboardNavigationLabelProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IDataSource, ITreeNode, ITreeRenderer, ITreeSorter, ITreeFilter } from 'vs/base/browser/ui/tree/tree'; -import { values, forEach } from 'vs/base/common/collections'; +import { values } from 'vs/base/common/collections'; import { createMatches, FuzzyScore } from 'vs/base/common/filters'; import 'vs/css!./media/outlineTree'; import 'vs/css!./media/symbol-icons'; @@ -22,6 +22,8 @@ import { MarkerSeverity } from 'vs/platform/markers/common/markers'; import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { registerColor, listErrorForeground, listWarningForeground, foreground } from 'vs/platform/theme/common/colorRegistry'; import { IdleValue } from 'vs/base/common/async'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { URI } from 'vs/base/common/uri'; export type OutlineItem = OutlineGroup | OutlineElement; @@ -278,27 +280,26 @@ export class OutlineFilter implements ITreeFilter { [SymbolKind.TypeParameter]: 'showTypeParameters', }); - private readonly _filteredTypes = new Set(); - constructor( private readonly _prefix: string, - @IConfigurationService private readonly _configService: IConfigurationService, - ) { - this.update(); - } - - update() { - this._filteredTypes.clear(); - forEach(OutlineFilter.configNameToKind, entry => { - const key = `${this._prefix}.${entry.key}`; - if (this._configService.getValue(key) === false) { - this._filteredTypes.add(entry.value); - } - }); - } + @ITextResourceConfigurationService private readonly _textResourceConfigService: ITextResourceConfigurationService, + ) { } filter(element: OutlineItem): boolean { - return !(element instanceof OutlineElement) || !this._filteredTypes.has(element.symbol.kind); + const outline = OutlineModel.get(element); + let uri: URI | undefined; + + if (outline) { + uri = outline.textModel.uri; + } + + if (!(element instanceof OutlineElement)) { + return true; + } + + const configName = (OutlineFilter.kindToConfigName as any)[element.symbol.kind] as string; + const configKey = `${this._prefix}.${configName}`; + return this._textResourceConfigService.getValue(uri, configKey); } } diff --git a/src/vs/workbench/contrib/outline/browser/outline.contribution.ts b/src/vs/workbench/contrib/outline/browser/outline.contribution.ts index b99786acfc2..6ebf4755f5a 100644 --- a/src/vs/workbench/contrib/outline/browser/outline.contribution.ts +++ b/src/vs/workbench/contrib/outline/browser/outline.contribution.ts @@ -55,92 +55,110 @@ Registry.as(ConfigurationExtensions.Configuration).regis }, 'outline.showFiles': { type: 'boolean', + overridable: true, default: true, markdownDescription: localize('filteredTypes.file', "When enabled outline shows `file`-symbols.") }, 'outline.showModules': { type: 'boolean', + overridable: true, default: true, markdownDescription: localize('filteredTypes.module', "When enabled outline shows `module`-symbols.") }, 'outline.showNamespaces': { type: 'boolean', default: true, + overridable: true, markdownDescription: localize('filteredTypes.namespace', "When enabled outline shows `namespace`-symbols.") }, 'outline.showPackages': { type: 'boolean', default: true, + overridable: true, markdownDescription: localize('filteredTypes.package', "When enabled outline shows `package`-symbols.") }, 'outline.showClasses': { type: 'boolean', default: true, + overridable: true, markdownDescription: localize('filteredTypes.class', "When enabled outline shows `class`-symbols.") }, 'outline.showMethods': { type: 'boolean', default: true, + overridable: true, markdownDescription: localize('filteredTypes.method', "When enabled outline shows `method`-symbols.") }, 'outline.showProperties': { type: 'boolean', default: true, + overridable: true, markdownDescription: localize('filteredTypes.property', "When enabled outline shows `property`-symbols.") }, 'outline.showFields': { type: 'boolean', default: true, + overridable: true, markdownDescription: localize('filteredTypes.field', "When enabled outline shows `field`-symbols.") }, 'outline.showConstructors': { type: 'boolean', default: true, + overridable: true, markdownDescription: localize('filteredTypes.constructor', "When enabled outline shows `constructor`-symbols.") }, 'outline.showEnums': { type: 'boolean', default: true, + overridable: true, markdownDescription: localize('filteredTypes.enum', "When enabled outline shows `enum`-symbols.") }, 'outline.showInterfaces': { type: 'boolean', default: true, + overridable: true, markdownDescription: localize('filteredTypes.interface', "When enabled outline shows `interface`-symbols.") }, 'outline.showFunctions': { type: 'boolean', default: true, + overridable: true, markdownDescription: localize('filteredTypes.function', "When enabled outline shows `function`-symbols.") }, 'outline.showVariables': { type: 'boolean', default: true, + overridable: true, markdownDescription: localize('filteredTypes.variable', "When enabled outline shows `variable`-symbols.") }, 'outline.showConstants': { type: 'boolean', default: true, + overridable: true, markdownDescription: localize('filteredTypes.constant', "When enabled outline shows `constant`-symbols.") }, 'outline.showStrings': { type: 'boolean', default: true, + overridable: true, markdownDescription: localize('filteredTypes.string', "When enabled outline shows `string`-symbols.") }, 'outline.showNumbers': { type: 'boolean', default: true, + overridable: true, markdownDescription: localize('filteredTypes.number', "When enabled outline shows `number`-symbols.") }, 'outline.showBooleans': { type: 'boolean', + overridable: true, default: true, markdownDescription: localize('filteredTypes.boolean', "When enabled outline shows `boolean`-symbols.") }, 'outline.showArrays': { type: 'boolean', default: true, + overridable: true, markdownDescription: localize('filteredTypes.array', "When enabled outline shows `array`-symbols.") }, 'outline.showObjects': { diff --git a/src/vs/workbench/contrib/outline/browser/outlineNavigation.ts b/src/vs/workbench/contrib/outline/browser/outlineNavigation.ts index 9b6e7c96e1f..c67f900e69e 100644 --- a/src/vs/workbench/contrib/outline/browser/outlineNavigation.ts +++ b/src/vs/workbench/contrib/outline/browser/outlineNavigation.ts @@ -17,9 +17,9 @@ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { forEach } from 'vs/base/common/collections'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { OutlineFilter } from 'vs/editor/contrib/documentSymbols/outlineTree'; import { binarySearch } from 'vs/base/common/arrays'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; class FlatOutline { @@ -78,7 +78,7 @@ export class OutlineNavigation implements IEditorContribution { constructor( editor: ICodeEditor, - @IConfigurationService private readonly _configService: IConfigurationService, + @ITextResourceConfigurationService private readonly _textResourceConfigService: ITextResourceConfigurationService, ) { this._editor = editor; } @@ -104,7 +104,7 @@ export class OutlineNavigation implements IEditorContribution { this._cts = new EditorStateCancellationTokenSource(this._editor, CodeEditorStateFlag.Position | CodeEditorStateFlag.Value | CodeEditorStateFlag.Scroll); - const filter = new OutlineFilter('outline', this._configService); + const filter = new OutlineFilter('outline', this._textResourceConfigService); const outlineModel = await OutlineModel.create(textModel, this._cts.token); if (this._cts.token.isCancellationRequested) { diff --git a/src/vs/workbench/contrib/outline/browser/outlinePanel.ts b/src/vs/workbench/contrib/outline/browser/outlinePanel.ts index 9d563e905c0..67503e0a4d3 100644 --- a/src/vs/workbench/contrib/outline/browser/outlinePanel.ts +++ b/src/vs/workbench/contrib/outline/browser/outlinePanel.ts @@ -373,7 +373,6 @@ export class OutlinePanel extends ViewletPanel { this._tree.updateChildren(); } if (e.affectsConfiguration('outline')) { - this._treeFilter.update(); this._tree.refilter(); } })); From 9497acd875493a3dd6d240ea401422b3cc797fda Mon Sep 17 00:00:00 2001 From: grey275 Date: Tue, 19 Nov 2019 14:35:18 -0800 Subject: [PATCH 005/255] explain specific usage of e.affectsConfiguration --- src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts index 4a9ee9ec117..565a8ebcb9e 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts @@ -150,7 +150,8 @@ export class EditorBreadcrumbsModel { const editorModel = this._editor.getModel() as ITextModel; const languageName = editorModel.getLanguageIdentifier().language; - // checking for changes in the current language override config + // Checking for changes in the current language override config. + // We can't be more specific than this because the ConfigurationChangeEvent(e) only includes the first part of the root path if (e.affectsConfiguration(`[${languageName}]`)) { this._updateOutline(true); } From 33d8569adf14370e7e8036d438ad35d47da137c7 Mon Sep 17 00:00:00 2001 From: grey275 Date: Wed, 20 Nov 2019 08:39:26 -0800 Subject: [PATCH 006/255] cange condition on outline tree refiltering to bring it in line with new OutlineFilter implementation --- src/vs/workbench/contrib/outline/browser/outlinePanel.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/outline/browser/outlinePanel.ts b/src/vs/workbench/contrib/outline/browser/outlinePanel.ts index 67503e0a4d3..2b93ee7c07d 100644 --- a/src/vs/workbench/contrib/outline/browser/outlinePanel.ts +++ b/src/vs/workbench/contrib/outline/browser/outlinePanel.ts @@ -372,7 +372,9 @@ export class OutlinePanel extends ViewletPanel { if (e.affectsConfiguration(OutlineConfigKeys.icons)) { this._tree.updateChildren(); } - if (e.affectsConfiguration('outline')) { + // This is a temporary solution to try and minimize refilters while + // ConfigurationChangeEvents only provide the first section of the config path. + if (e.affectedKeys.some(key => key.search(/(outline|\[\w+\])/))) { this._tree.refilter(); } })); From e3eaf02f577fb67752165bb4aadfb3d0e921ac53 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 25 Nov 2019 06:36:01 -0800 Subject: [PATCH 007/255] Setup ESRP code signing for rpm Fixes #78727 --- .../linux/product-build-linux.yml | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index 6e1f8ec1e53..a45cf9baf01 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -127,6 +127,30 @@ steps: ./build/azure-pipelines/linux/publish.sh displayName: Publish +- script: | + set -e + pushd ../VSCode-linux-x64 && zip -r -X -y ../VSCode-linux-x64.zip * && popd + displayName: Archive rpm for signing + +- task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1 + inputs: + ConnectedServiceName: 'ESRP CodeSign' + FolderPath: '$(agent.builddirectory)' + Pattern: 'VSCode-linux-x64.zip' + signConfigType: inlineSignParams + inlineOperation: | + [ + { + "keyCode": "CP-450778-Pgp", + "operationSetCode": "LinuxSign", + "parameters": [ ], + "toolName": "sign", + "toolVersion": "1.0" + } + ] + SessionTimeout: 120 + displayName: Codesign + - task: PublishPipelineArtifact@0 displayName: 'Publish Pipeline Artifact' inputs: From 5cb35d3bfcac4deb49d525c50c06fd133ba379da Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 25 Nov 2019 07:11:25 -0800 Subject: [PATCH 008/255] Set rpm path for sign --- build/azure-pipelines/linux/product-build-linux.yml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index a45cf9baf01..183c8f9ffd7 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -127,16 +127,11 @@ steps: ./build/azure-pipelines/linux/publish.sh displayName: Publish -- script: | - set -e - pushd ../VSCode-linux-x64 && zip -r -X -y ../VSCode-linux-x64.zip * && popd - displayName: Archive rpm for signing - - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1 inputs: ConnectedServiceName: 'ESRP CodeSign' - FolderPath: '$(agent.builddirectory)' - Pattern: 'VSCode-linux-x64.zip' + FolderPath: '$(agent.builddirectory)/.build/linux/rpm/x86_64' + Pattern: '*.rpm' signConfigType: inlineSignParams inlineOperation: | [ From 47cc448a15a89ab695db48c34fc218a804ca4bcc Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 25 Nov 2019 07:55:23 -0800 Subject: [PATCH 009/255] Log paths --- build/azure-pipelines/linux/product-build-linux.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index 183c8f9ffd7..b8afaaa6d53 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -127,6 +127,15 @@ steps: ./build/azure-pipelines/linux/publish.sh displayName: Publish +- script: | + ls -lR . + displayName: Debug log 1 + +- script: | + apt-get install tree + tree + displayName: Debug log 2 + - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1 inputs: ConnectedServiceName: 'ESRP CodeSign' From 01b295dd4104874abdeff166c13d8b4d7c43a14b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 25 Nov 2019 10:21:34 -0800 Subject: [PATCH 010/255] Update log --- build/azure-pipelines/linux/product-build-linux.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index b8afaaa6d53..96c75405c54 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -128,14 +128,13 @@ steps: displayName: Publish - script: | - ls -lR . + pwd + echo '******* .build' + ls -lR .build + echo '******* ..' + ls -lR .. displayName: Debug log 1 -- script: | - apt-get install tree - tree - displayName: Debug log 2 - - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1 inputs: ConnectedServiceName: 'ESRP CodeSign' From 323dc09e48727ec90f56bb1eb4f390164e0c776f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 25 Nov 2019 11:05:21 -0800 Subject: [PATCH 011/255] Try new folder path --- build/azure-pipelines/linux/product-build-linux.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index 96c75405c54..2ba9457bc71 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -138,7 +138,7 @@ steps: - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1 inputs: ConnectedServiceName: 'ESRP CodeSign' - FolderPath: '$(agent.builddirectory)/.build/linux/rpm/x86_64' + FolderPath: '.build/linux/rpm/x86_64' Pattern: '*.rpm' signConfigType: inlineSignParams inlineOperation: | From 22cbe0c912b2d641ec4aec8e3ae7457f97b3c6c0 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 27 Nov 2019 09:57:21 -0800 Subject: [PATCH 012/255] Sign rpm before publishing --- .../linux/product-build-linux.yml | 28 +++++++------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index 2ba9457bc71..6743d997729 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -118,26 +118,9 @@ steps: displayName: Run integration tests condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) -- script: | - set -e - AZURE_DOCUMENTDB_MASTERKEY="$(builds-docdb-key-readwrite)" \ - AZURE_STORAGE_ACCESS_KEY_2="$(vscode-storage-key)" \ - VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ - VSCODE_HOCKEYAPP_TOKEN="$(vscode-hockeyapp-token)" \ - ./build/azure-pipelines/linux/publish.sh - displayName: Publish - -- script: | - pwd - echo '******* .build' - ls -lR .build - echo '******* ..' - ls -lR .. - displayName: Debug log 1 - - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1 inputs: - ConnectedServiceName: 'ESRP CodeSign' + ConnectedServiceName: 'ESRP CodeSign rpm' FolderPath: '.build/linux/rpm/x86_64' Pattern: '*.rpm' signConfigType: inlineSignParams @@ -154,6 +137,15 @@ steps: SessionTimeout: 120 displayName: Codesign +- script: | + set -e + AZURE_DOCUMENTDB_MASTERKEY="$(builds-docdb-key-readwrite)" \ + AZURE_STORAGE_ACCESS_KEY_2="$(vscode-storage-key)" \ + VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ + VSCODE_HOCKEYAPP_TOKEN="$(vscode-hockeyapp-token)" \ + ./build/azure-pipelines/linux/publish.sh + displayName: Publish + - task: PublishPipelineArtifact@0 displayName: 'Publish Pipeline Artifact' inputs: From a079c71934eec704f5a9de4478ca1c0151799d0a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 27 Nov 2019 10:00:49 -0800 Subject: [PATCH 013/255] Fix display name --- build/azure-pipelines/linux/product-build-linux.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index 6743d997729..98d4b094253 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -120,7 +120,7 @@ steps: - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1 inputs: - ConnectedServiceName: 'ESRP CodeSign rpm' + ConnectedServiceName: 'ESRP CodeSign' FolderPath: '.build/linux/rpm/x86_64' Pattern: '*.rpm' signConfigType: inlineSignParams @@ -135,7 +135,7 @@ steps: } ] SessionTimeout: 120 - displayName: Codesign + displayName: Codesign rpm - script: | set -e From 9c8ff0429912c1fe8e3a64d432519a423409c6e2 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 27 Nov 2019 10:33:38 -0800 Subject: [PATCH 014/255] Move Linux package build into new step --- build/azure-pipelines/linux/product-build-linux.yml | 7 +++++++ build/azure-pipelines/linux/publish.sh | 4 ---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index 98d4b094253..972962dfcff 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -118,6 +118,13 @@ steps: displayName: Run integration tests condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) +- script: | + set -e + yarn gulp "vscode-linux-x64-build-deb" + yarn gulp "vscode-linux-x64-build-rpm" + yarn gulp "vscode-linux-x64-prepare-snap" + displayName: Build packages + - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1 inputs: ConnectedServiceName: 'ESRP CodeSign' diff --git a/build/azure-pipelines/linux/publish.sh b/build/azure-pipelines/linux/publish.sh index 3a5f9683eaa..3da6ea3eed6 100755 --- a/build/azure-pipelines/linux/publish.sh +++ b/build/azure-pipelines/linux/publish.sh @@ -31,7 +31,6 @@ node build/azure-pipelines/common/createAsset.js "server-$PLATFORM_LINUX" archiv node build/azure-pipelines/common/symbols.js "$VSCODE_MIXIN_PASSWORD" "$VSCODE_HOCKEYAPP_TOKEN" "x64" "$VSCODE_HOCKEYAPP_ID_LINUX64" # Publish DEB -yarn gulp "vscode-linux-x64-build-deb" PLATFORM_DEB="linux-deb-x64" DEB_ARCH="amd64" DEB_FILENAME="$(ls $REPO/.build/linux/deb/$DEB_ARCH/deb/)" @@ -40,7 +39,6 @@ DEB_PATH="$REPO/.build/linux/deb/$DEB_ARCH/deb/$DEB_FILENAME" node build/azure-pipelines/common/createAsset.js "$PLATFORM_DEB" package "$DEB_FILENAME" "$DEB_PATH" # Publish RPM -yarn gulp "vscode-linux-x64-build-rpm" PLATFORM_RPM="linux-rpm-x64" RPM_ARCH="x86_64" RPM_FILENAME="$(ls $REPO/.build/linux/rpm/$RPM_ARCH/ | grep .rpm)" @@ -49,8 +47,6 @@ RPM_PATH="$REPO/.build/linux/rpm/$RPM_ARCH/$RPM_FILENAME" node build/azure-pipelines/common/createAsset.js "$PLATFORM_RPM" package "$RPM_FILENAME" "$RPM_PATH" # Publish Snap -yarn gulp "vscode-linux-x64-prepare-snap" - # Pack snap tarball artifact, in order to preserve file perms mkdir -p $REPO/.build/linux/snap-tarball SNAP_TARBALL_PATH="$REPO/.build/linux/snap-tarball/snap-x64.tar.gz" From e88573e26b71e1471dd1abc6458c6cb7fefba17d Mon Sep 17 00:00:00 2001 From: grey275 Date: Thu, 28 Nov 2019 12:15:22 -0800 Subject: [PATCH 015/255] get rid ofunnecessary typecasting --- src/vs/editor/contrib/documentSymbols/outlineTree.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/documentSymbols/outlineTree.ts b/src/vs/editor/contrib/documentSymbols/outlineTree.ts index 298ba5ec5e1..b45f6f13e6c 100644 --- a/src/vs/editor/contrib/documentSymbols/outlineTree.ts +++ b/src/vs/editor/contrib/documentSymbols/outlineTree.ts @@ -297,7 +297,7 @@ export class OutlineFilter implements ITreeFilter { return true; } - const configName = (OutlineFilter.kindToConfigName as any)[element.symbol.kind] as string; + const configName = OutlineFilter.kindToConfigName[element.symbol.kind]; const configKey = `${this._prefix}.${configName}`; return this._textResourceConfigService.getValue(uri, configKey); } From 80db12a5ecc4b3b2e4f0f14a36e3f997355c9aac Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 29 Nov 2019 15:09:26 +0100 Subject: [PATCH 016/255] move API to stable --- src/vs/vscode.d.ts | 236 +++++++++++++++++ src/vs/vscode.proposed.d.ts | 242 ------------------ .../workbench/api/common/extHost.api.impl.ts | 6 - 3 files changed, 236 insertions(+), 248 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index a21f1a0ec4a..0c00be8daa6 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -8084,6 +8084,172 @@ declare module 'vscode' { waitUntil(thenable: Thenable): void; } + /** + * An event that is fired when files are going to be created. + * + * To make modifications to the workspace before the files are created, + * call the [`waitUntil](#FileWillCreateEvent.waitUntil)-function with a + * thenable that resolves to a [workspace edit](#WorkspaceEdit). + */ + export interface FileWillCreateEvent { + + /** + * The files that are going to be created. + */ + readonly files: ReadonlyArray; + + /** + * Allows to pause the event and to apply a [workspace edit](#WorkspaceEdit). + * + * *Note:* This function can only be called during event dispatch and not + * in an asynchronous manner: + * + * ```ts + * workspace.onWillCreateFiles(event => { + * // async, will *throw* an error + * setTimeout(() => event.waitUntil(promise)); + * + * // sync, OK + * event.waitUntil(promise); + * }) + * ``` + * + * @param thenable A thenable that delays saving. + */ + waitUntil(thenable: Thenable): void; + + /** + * Allows to pause the event until the provided thenable resolves. + * + * *Note:* This function can only be called during event dispatch. + * + * @param thenable A thenable that delays saving. + */ + waitUntil(thenable: Thenable): void; + } + + /** + * An event that is fired after files are created. + */ + export interface FileCreateEvent { + + /** + * The files that got created. + */ + readonly files: ReadonlyArray; + } + + /** + * An event that is fired when files are going to be deleted. + * + * To make modifications to the workspace before the files are deleted, + * call the [`waitUntil](#FileWillCreateEvent.waitUntil)-function with a + * thenable that resolves to a [workspace edit](#WorkspaceEdit). + */ + export interface FileWillDeleteEvent { + + /** + * The files that are going to be deleted. + */ + readonly files: ReadonlyArray; + + /** + * Allows to pause the event and to apply a [workspace edit](#WorkspaceEdit). + * + * *Note:* This function can only be called during event dispatch and not + * in an asynchronous manner: + * + * ```ts + * workspace.onWillCreateFiles(event => { + * // async, will *throw* an error + * setTimeout(() => event.waitUntil(promise)); + * + * // sync, OK + * event.waitUntil(promise); + * }) + * ``` + * + * @param thenable A thenable that delays saving. + */ + waitUntil(thenable: Thenable): void; + + /** + * Allows to pause the event until the provided thenable resolves. + * + * *Note:* This function can only be called during event dispatch. + * + * @param thenable A thenable that delays saving. + */ + waitUntil(thenable: Thenable): void; + } + + /** + * An event that is fired after files are deleted. + */ + export interface FileDeleteEvent { + + /** + * The files that got deleted. + */ + readonly files: ReadonlyArray; + } + + /** + * An event that is fired when files are going to be renamed. + * + * To make modifications to the workspace before the files are renamed, + * call the [`waitUntil](#FileWillCreateEvent.waitUntil)-function with a + * thenable that resolves to a [workspace edit](#WorkspaceEdit). + */ + export interface FileWillRenameEvent { + + /** + * The files that are going to be renamed. + */ + readonly files: ReadonlyArray<{ oldUri: Uri, newUri: Uri }>; + + /** + * Allows to pause the event and to apply a [workspace edit](#WorkspaceEdit). + * + * *Note:* This function can only be called during event dispatch and not + * in an asynchronous manner: + * + * ```ts + * workspace.onWillCreateFiles(event => { + * // async, will *throw* an error + * setTimeout(() => event.waitUntil(promise)); + * + * // sync, OK + * event.waitUntil(promise); + * }) + * ``` + * + * @param thenable A thenable that delays saving. + */ + waitUntil(thenable: Thenable): void; + + /** + * Allows to pause the event until the provided thenable resolves. + * + * *Note:* This function can only be called during event dispatch. + * + * @param thenable A thenable that delays saving. + */ + waitUntil(thenable: Thenable): void; + } + + /** + * An event that is fired after files are renamed. + */ + export interface FileRenameEvent { + + /** + * The files that got renamed. + */ + readonly files: ReadonlyArray<{ oldUri: Uri, newUri: Uri }>; + } + + /** * An event describing a change to the set of [workspace folders](#workspace.workspaceFolders). */ @@ -8433,6 +8599,76 @@ declare module 'vscode' { */ export const onDidSaveTextDocument: Event; + /** + * An event that is emitted when files are being created. + * + * *Note 1:* This event is triggered by user gestures, like creating a file from the + * explorer, or from the [`workspace.applyEdit`](#workspace.applyEdit)-api. This event is *not* fired when + * files change on disk, e.g triggered by another application, or when using the + * [`workspace.fs`](#FileSystem)-api. + * + * *Note 2:* When this event is fired, edits to files thare are being created cannot be applied. + */ + export const onWillCreateFiles: Event; + + /** + * An event that is emitted when files have been created. + * + * *Note:* This event is triggered by user gestures, like creating a file from the + * explorer, or from the [`workspace.applyEdit`](#workspace.applyEdit)-api, but this event is *not* fired when + * files change on disk, e.g triggered by another application, or when using the + * [`workspace.fs`](#FileSystem)-api. + */ + export const onDidCreateFiles: Event; + + /** + * An event that is emitted when files are being deleted. + * + * *Note 1:* This event is triggered by user gestures, like deleting a file from the + * explorer, or from the [`workspace.applyEdit`](#workspace.applyEdit)-api, but this event is *not* fired when + * files change on disk, e.g triggered by another application, or when using the + * [`workspace.fs`](#FileSystem)-api. + * + * *Note 2:* When deleting a folder with children only one event is fired. + */ + export const onWillDeleteFiles: Event; + + /** + * An event that is emitted when files have been deleted. + * + * *Note 1:* This event is triggered by user gestures, like deleting a file from the + * explorer, or from the [`workspace.applyEdit`](#workspace.applyEdit)-api, but this event is *not* fired when + * files change on disk, e.g triggered by another application, or when using the + * [`workspace.fs`](#FileSystem)-api. + * + * *Note 2:* When deleting a folder with children only one event is fired. + */ + export const onDidDeleteFiles: Event; + + /** + * An event that is emitted when files are being renamed. + * + * *Note 1:* This event is triggered by user gestures, like renaming a file from the + * explorer, and from the [`workspace.applyEdit`](#workspace.applyEdit)-api, but this event is *not* fired when + * files change on disk, e.g triggered by another application, or when using the + * [`workspace.fs`](#FileSystem)-api. + * + * *Note 2:* When renaming a folder with children only one event is fired. + */ + export const onWillRenameFiles: Event; + + /** + * An event that is emitted when files have been renamed. + * + * *Note 1:* This event is triggered by user gestures, like renaming a file from the + * explorer, and from the [`workspace.applyEdit`](#workspace.applyEdit)-api, but this event is *not* fired when + * files change on disk, e.g triggered by another application, or when using the + * [`workspace.fs`](#FileSystem)-api. + * + * *Note 2:* When renaming a folder with children only one event is fired. + */ + export const onDidRenameFiles: Event; + /** * Get a workspace configuration object. * diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 9241ab3e662..13abf220667 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -736,7 +736,6 @@ declare module 'vscode' { //#endregion - //#region Terminal /** @@ -830,247 +829,6 @@ declare module 'vscode' { //#endregion - //#region mjbvz,joh: https://github.com/Microsoft/vscode/issues/43768 - - /** - * An event that is fired when files are going to be created. - * - * To make modifications to the workspace before the files are created, - * call the [`waitUntil](#FileWillCreateEvent.waitUntil)-function with a - * thenable that resolves to a [workspace edit](#WorkspaceEdit). - */ - export interface FileWillCreateEvent { - - /** - * The files that are going to be created. - */ - readonly files: ReadonlyArray; - - /** - * Allows to pause the event and to apply a [workspace edit](#WorkspaceEdit). - * - * *Note:* This function can only be called during event dispatch and not - * in an asynchronous manner: - * - * ```ts - * workspace.onWillCreateFiles(event => { - * // async, will *throw* an error - * setTimeout(() => event.waitUntil(promise)); - * - * // sync, OK - * event.waitUntil(promise); - * }) - * ``` - * - * @param thenable A thenable that delays saving. - */ - waitUntil(thenable: Thenable): void; - - /** - * Allows to pause the event until the provided thenable resolves. - * - * *Note:* This function can only be called during event dispatch. - * - * @param thenable A thenable that delays saving. - */ - waitUntil(thenable: Thenable): void; - } - - /** - * An event that is fired after files are created. - */ - export interface FileCreateEvent { - - /** - * The files that got created. - */ - readonly files: ReadonlyArray; - } - - /** - * An event that is fired when files are going to be deleted. - * - * To make modifications to the workspace before the files are deleted, - * call the [`waitUntil](#FileWillCreateEvent.waitUntil)-function with a - * thenable that resolves to a [workspace edit](#WorkspaceEdit). - */ - export interface FileWillDeleteEvent { - - /** - * The files that are going to be deleted. - */ - readonly files: ReadonlyArray; - - /** - * Allows to pause the event and to apply a [workspace edit](#WorkspaceEdit). - * - * *Note:* This function can only be called during event dispatch and not - * in an asynchronous manner: - * - * ```ts - * workspace.onWillCreateFiles(event => { - * // async, will *throw* an error - * setTimeout(() => event.waitUntil(promise)); - * - * // sync, OK - * event.waitUntil(promise); - * }) - * ``` - * - * @param thenable A thenable that delays saving. - */ - waitUntil(thenable: Thenable): void; - - /** - * Allows to pause the event until the provided thenable resolves. - * - * *Note:* This function can only be called during event dispatch. - * - * @param thenable A thenable that delays saving. - */ - waitUntil(thenable: Thenable): void; - } - - /** - * An event that is fired after files are deleted. - */ - export interface FileDeleteEvent { - - /** - * The files that got deleted. - */ - readonly files: ReadonlyArray; - } - - /** - * An event that is fired when files are going to be renamed. - * - * To make modifications to the workspace before the files are renamed, - * call the [`waitUntil](#FileWillCreateEvent.waitUntil)-function with a - * thenable that resolves to a [workspace edit](#WorkspaceEdit). - */ - export interface FileWillRenameEvent { - - /** - * The files that are going to be renamed. - */ - readonly files: ReadonlyArray<{ oldUri: Uri, newUri: Uri }>; - - /** - * Allows to pause the event and to apply a [workspace edit](#WorkspaceEdit). - * - * *Note:* This function can only be called during event dispatch and not - * in an asynchronous manner: - * - * ```ts - * workspace.onWillCreateFiles(event => { - * // async, will *throw* an error - * setTimeout(() => event.waitUntil(promise)); - * - * // sync, OK - * event.waitUntil(promise); - * }) - * ``` - * - * @param thenable A thenable that delays saving. - */ - waitUntil(thenable: Thenable): void; - - /** - * Allows to pause the event until the provided thenable resolves. - * - * *Note:* This function can only be called during event dispatch. - * - * @param thenable A thenable that delays saving. - */ - waitUntil(thenable: Thenable): void; - } - - /** - * An event that is fired after files are renamed. - */ - export interface FileRenameEvent { - - /** - * The files that got renamed. - */ - readonly files: ReadonlyArray<{ oldUri: Uri, newUri: Uri }>; - } - - export namespace workspace { - - /** - * An event that is emitted when files are being created. - * - * *Note 1:* This event is triggered by user gestures, like creating a file from the - * explorer, or from the [`workspace.applyEdit`](#workspace.applyEdit)-api. This event is *not* fired when - * files change on disk, e.g triggered by another application, or when using the - * [`workspace.fs`](#FileSystem)-api. - * - * *Note 2:* When this event is fired, edits to files thare are being created cannot be applied. - */ - export const onWillCreateFiles: Event; - - /** - * An event that is emitted when files have been created. - * - * *Note:* This event is triggered by user gestures, like creating a file from the - * explorer, or from the [`workspace.applyEdit`](#workspace.applyEdit)-api, but this event is *not* fired when - * files change on disk, e.g triggered by another application, or when using the - * [`workspace.fs`](#FileSystem)-api. - */ - export const onDidCreateFiles: Event; - - /** - * An event that is emitted when files are being deleted. - * - * *Note 1:* This event is triggered by user gestures, like deleting a file from the - * explorer, or from the [`workspace.applyEdit`](#workspace.applyEdit)-api, but this event is *not* fired when - * files change on disk, e.g triggered by another application, or when using the - * [`workspace.fs`](#FileSystem)-api. - * - * *Note 2:* When deleting a folder with children only one event is fired. - */ - export const onWillDeleteFiles: Event; - - /** - * An event that is emitted when files have been deleted. - * - * *Note 1:* This event is triggered by user gestures, like deleting a file from the - * explorer, or from the [`workspace.applyEdit`](#workspace.applyEdit)-api, but this event is *not* fired when - * files change on disk, e.g triggered by another application, or when using the - * [`workspace.fs`](#FileSystem)-api. - * - * *Note 2:* When deleting a folder with children only one event is fired. - */ - export const onDidDeleteFiles: Event; - - /** - * An event that is emitted when files are being renamed. - * - * *Note 1:* This event is triggered by user gestures, like renaming a file from the - * explorer, and from the [`workspace.applyEdit`](#workspace.applyEdit)-api, but this event is *not* fired when - * files change on disk, e.g triggered by another application, or when using the - * [`workspace.fs`](#FileSystem)-api. - * - * *Note 2:* When renaming a folder with children only one event is fired. - */ - export const onWillRenameFiles: Event; - - /** - * An event that is emitted when files have been renamed. - * - * *Note 1:* This event is triggered by user gestures, like renaming a file from the - * explorer, and from the [`workspace.applyEdit`](#workspace.applyEdit)-api, but this event is *not* fired when - * files change on disk, e.g triggered by another application, or when using the - * [`workspace.fs`](#FileSystem)-api. - * - * *Note 2:* When renaming a folder with children only one event is fired. - */ - export const onDidRenameFiles: Event; - } - //#endregion - //#region Alex - OnEnter enhancement export interface OnEnterRule { /** diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 84a355928e9..32ad205cba6 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -699,27 +699,21 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostLabelService.$registerResourceLabelFormatter(formatter); }, onDidCreateFiles: (listener, thisArg, disposables) => { - checkProposedApiEnabled(extension); return extHostFileSystemEvent.onDidCreateFile(listener, thisArg, disposables); }, onDidDeleteFiles: (listener, thisArg, disposables) => { - checkProposedApiEnabled(extension); return extHostFileSystemEvent.onDidDeleteFile(listener, thisArg, disposables); }, onDidRenameFiles: (listener, thisArg, disposables) => { - checkProposedApiEnabled(extension); return extHostFileSystemEvent.onDidRenameFile(listener, thisArg, disposables); }, onWillCreateFiles: (listener: (e: vscode.FileWillCreateEvent) => any, thisArg?: any, disposables?: vscode.Disposable[]) => { - checkProposedApiEnabled(extension); return extHostFileSystemEvent.getOnWillCreateFileEvent(extension)(listener, thisArg, disposables); }, onWillDeleteFiles: (listener: (e: vscode.FileWillDeleteEvent) => any, thisArg?: any, disposables?: vscode.Disposable[]) => { - checkProposedApiEnabled(extension); return extHostFileSystemEvent.getOnWillDeleteFileEvent(extension)(listener, thisArg, disposables); }, onWillRenameFiles: (listener: (e: vscode.FileWillRenameEvent) => any, thisArg?: any, disposables?: vscode.Disposable[]) => { - checkProposedApiEnabled(extension); return extHostFileSystemEvent.getOnWillRenameFileEvent(extension)(listener, thisArg, disposables); } }; From 154275c2e1a4ede6b4108906c69b7c30db41e3a0 Mon Sep 17 00:00:00 2001 From: Andre Weinand Date: Fri, 29 Nov 2019 18:40:56 +0100 Subject: [PATCH 017/255] remove proposed type 'DebugAdapterInlineImplementation' from vscode.d.ts for now --- extensions/vscode-api-tests/src/extension.ts | 2 +- src/vs/vscode.d.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/vscode-api-tests/src/extension.ts b/extensions/vscode-api-tests/src/extension.ts index fd713b5065c..3f4a6dfd151 100644 --- a/extensions/vscode-api-tests/src/extension.ts +++ b/extensions/vscode-api-tests/src/extension.ts @@ -3817,7 +3817,7 @@ export class MockDebugAdapterDescriptorFactory implements vscode.DebugAdapterDes } createDebugAdapterDescriptor(_session: vscode.DebugSession, _executable: vscode.DebugAdapterExecutable | undefined): vscode.ProviderResult { - return new vscode.DebugAdapterInlineImplementation(new MockDebugSession(this.memfs)); + return new vscode.DebugAdapterInlineImplementation(new MockDebugSession(this.memfs)); } } diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 32bdd775c31..060cd82e81e 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -9341,7 +9341,7 @@ declare module 'vscode' { constructor(port: number, host?: string); } - export type DebugAdapterDescriptor = DebugAdapterExecutable | DebugAdapterServer | DebugAdapterInlineImplementation; + export type DebugAdapterDescriptor = DebugAdapterExecutable | DebugAdapterServer; export interface DebugAdapterDescriptorFactory { /** From a5ce0f26cde56ee53972d5c7154406b032177e87 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 29 Nov 2019 12:16:08 -0800 Subject: [PATCH 018/255] Bump node2 --- build/builtInExtensions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/builtInExtensions.json b/build/builtInExtensions.json index b75cca4234e..ebdfc048f62 100644 --- a/build/builtInExtensions.json +++ b/build/builtInExtensions.json @@ -16,7 +16,7 @@ }, { "name": "ms-vscode.node-debug2", - "version": "1.41.0", + "version": "1.41.1", "repo": "https://github.com/Microsoft/vscode-node-debug2", "metadata": { "id": "36d19e17-7569-4841-a001-947eb18602b2", From 665b5762600dcf36311dfefe049d86f3025687c1 Mon Sep 17 00:00:00 2001 From: Andre Weinand Date: Sat, 30 Nov 2019 00:52:05 +0100 Subject: [PATCH 019/255] some tweaks for inline debug adaper API; see #85544 --- extensions/vscode-api-tests/src/extension.ts | 4 +--- src/vs/vscode.proposed.d.ts | 17 +++++++++++++---- .../workbench/api/common/extHostDebugService.ts | 9 +++------ src/vs/workbench/contrib/debug/common/debug.ts | 4 +--- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/extensions/vscode-api-tests/src/extension.ts b/extensions/vscode-api-tests/src/extension.ts index 3f4a6dfd151..162528cecea 100644 --- a/extensions/vscode-api-tests/src/extension.ts +++ b/extensions/vscode-api-tests/src/extension.ts @@ -2824,14 +2824,12 @@ export class ProtocolServer implements vscode.DebugAdapter { private _sequence: number = 1; private _pendingRequests = new Map void>(); - constructor() { - } public handleMessage(message: DebugProtocol.ProtocolMessage): void { this.dispatch(message); } - public stop(): void { + public dispose() { } public sendEvent(event: DebugProtocol.Event): void { diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index c554c0d3c17..7e1407dc6cc 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -582,7 +582,7 @@ declare module 'vscode' { //#endregion - //#region André: debug + //#region André: debug API for inline debug adapters https://github.com/microsoft/vscode/issues/85544 /** * A DebugProtocolMessage is an opaque stand-in type for the [ProtocolMessage](https://microsoft.github.io/debug-adapter-protocol/specification#Base_Protocol_ProtocolMessage) type defined in the Debug Adapter Protocol. @@ -592,13 +592,22 @@ declare module 'vscode' { } /** - * A debug adapter that implements the Debug Adapter Protocol can be registered with VS Code if it implements this interface. + * A debug adapter that implements the Debug Adapter Protocol can be registered with VS Code if it implements the DebugAdapter interface. */ - export interface DebugAdapter { + export interface DebugAdapter extends Disposable { + /** + * An event which fires when the debug adapter sends a Debug Adapter Protocol message to VS Code. + * Messages can be requests, responses, or events. + */ readonly onSendMessage: Event; - readonly onError: Event; + /** + * Handle a Debug Adapter Protocol message. + * Messages can be requests, responses, or events. + * Results or errors are returned via onSendMessage events. + * @param message A Debug Adapter Protocol message + */ handleMessage(message: DebugProtocolMessage): void; } diff --git a/src/vs/workbench/api/common/extHostDebugService.ts b/src/vs/workbench/api/common/extHostDebugService.ts index eda288c27d4..499d4c257f7 100644 --- a/src/vs/workbench/api/common/extHostDebugService.ts +++ b/src/vs/workbench/api/common/extHostDebugService.ts @@ -1054,12 +1054,6 @@ class DirectDebugAdapter extends AbstractDebugAdapter { this.acceptMessage(message); }); } - - if (this.implementation.onError) { - implementation.onError((error: Error) => { - this._onError.fire(error); - }); - } } startSession(): Promise { @@ -1073,6 +1067,9 @@ class DirectDebugAdapter extends AbstractDebugAdapter { } stopSession(): Promise { + if (this.implementation.dispose) { + this.implementation.dispose(); + } return Promise.resolve(undefined); } } diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 092e1364740..b7795ef5a6e 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -547,10 +547,8 @@ export interface IDebugAdapterServer { readonly host?: string; } -export interface IDebugAdapterInlineImpl { +export interface IDebugAdapterInlineImpl extends IDisposable { readonly onSendMessage: Event; - readonly onError: Event; - handleMessage(message: DebugProtocol.Message): void; } From 55e3649e40fc307bd7a8881c22b0e1cb2436c783 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 29 Nov 2019 17:27:15 -0800 Subject: [PATCH 020/255] Bump node2 for #81397 --- build/builtInExtensions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/builtInExtensions.json b/build/builtInExtensions.json index ebdfc048f62..ec97bf7bc5e 100644 --- a/build/builtInExtensions.json +++ b/build/builtInExtensions.json @@ -16,7 +16,7 @@ }, { "name": "ms-vscode.node-debug2", - "version": "1.41.1", + "version": "1.41.2", "repo": "https://github.com/Microsoft/vscode-node-debug2", "metadata": { "id": "36d19e17-7569-4841-a001-947eb18602b2", From 43720071c243032f03d33f48fc5398ce1875f4de Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Sat, 30 Nov 2019 10:34:13 +0100 Subject: [PATCH 021/255] add iterator utils --- src/vs/base/common/iterator.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/vs/base/common/iterator.ts b/src/vs/base/common/iterator.ts index 3ea4bc94e32..9287a6eaebf 100644 --- a/src/vs/base/common/iterator.ts +++ b/src/vs/base/common/iterator.ts @@ -172,13 +172,28 @@ export module Iterator { } }; } + + export function chain(iterator: Iterator): ChainableIterator { + return new ChainableIterator(iterator); + } +} + +export class ChainableIterator implements Iterator { + + constructor(private it: Iterator) { } + + next(): IteratorResult { return this.it.next(); } + map(fn: (t: T) => R): ChainableIterator { return new ChainableIterator(Iterator.map(this.it, fn)); } + filter(fn: (t: T) => boolean): ChainableIterator { return new ChainableIterator(Iterator.filter(this.it, fn)); } } export type ISequence = Iterator | T[]; -export function getSequenceIterator(arg: Iterator | T[]): Iterator { +export function getSequenceIterator(arg: ISequence | undefined): Iterator { if (Array.isArray(arg)) { return Iterator.fromArray(arg); + } else if (!arg) { + return Iterator.empty(); } else { return arg; } From 087b69d386c9ce7d454ffcb6a5bfd3a06f11f362 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Sat, 30 Nov 2019 10:47:13 +0100 Subject: [PATCH 022/255] :lipstick: --- src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts b/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts index 293aae91c77..f1da4355047 100644 --- a/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts +++ b/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts @@ -11,7 +11,7 @@ import { IObjectTreeModelOptions, ObjectTreeModel, IObjectTreeModel } from 'vs/b // Exported only for test reasons, do not use directly export interface ICompressedTreeElement extends ITreeElement { - readonly children?: Iterator> | ICompressedTreeElement[]; + readonly children?: ISequence>; readonly incompressible?: boolean; } From 8839651655ed622493c2762710f3583bd17e3a78 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Sat, 30 Nov 2019 10:50:16 +0100 Subject: [PATCH 023/255] improve filter behavior in compressed async data tree fixes #85193 fixes #85835 --- src/vs/base/browser/ui/tree/asyncDataTree.ts | 50 +++++++++++++++++--- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index 06c2a70a9cf..d3a9859f0b5 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -6,7 +6,7 @@ import { ComposedTreeDelegate, IAbstractTreeOptions, IAbstractTreeOptionsUpdate } from 'vs/base/browser/ui/tree/abstractTree'; import { ObjectTree, IObjectTreeOptions, CompressibleObjectTree, ICompressibleTreeRenderer, ICompressibleKeyboardNavigationLabelProvider, ICompressibleObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree'; import { IListVirtualDelegate, IIdentityProvider, IListDragAndDrop, IListDragOverReaction } from 'vs/base/browser/ui/list/list'; -import { ITreeElement, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeSorter, ICollapseStateChangeEvent, IAsyncDataSource, ITreeDragAndDrop, TreeError, WeakMapper } from 'vs/base/browser/ui/tree/tree'; +import { ITreeElement, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeSorter, ICollapseStateChangeEvent, IAsyncDataSource, ITreeDragAndDrop, TreeError, WeakMapper, ITreeFilter, TreeVisibility, TreeFilterResult } from 'vs/base/browser/ui/tree/tree'; import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; import { timeout, CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; @@ -20,6 +20,7 @@ import { values } from 'vs/base/common/map'; import { ScrollEvent } from 'vs/base/common/scrollable'; import { ICompressedTreeNode, ICompressedTreeElement } from 'vs/base/browser/ui/tree/compressedObjectTreeModel'; import { IThemable } from 'vs/base/common/styler'; +import { isFilterResult, getVisibleState } from 'vs/base/browser/ui/tree/indexTreeModel'; interface IAsyncDataTreeNode { element: TInput | T; @@ -760,12 +761,7 @@ export class AsyncDataTree implements IDisposable result = createCancelablePromise(async () => { const children = await this.dataSource.getChildren(node.element!); - - if (this.sorter) { - children.sort(this.sorter.compare.bind(this.sorter)); - } - - return children; + return this.processChildren(children); }); this.refreshPromises.set(node, result); @@ -924,6 +920,14 @@ export class AsyncDataTree implements IDisposable }; } + protected processChildren(children: T[]): T[] { + if (this.sorter) { + children.sort(this.sorter.compare.bind(this.sorter)); + } + + return children; + } + // view state getViewState(): IAsyncDataTreeViewState { @@ -1066,6 +1070,7 @@ export class CompressibleAsyncDataTree extends As protected readonly tree!: CompressibleObjectTree, TFilterData>; protected readonly compressibleNodeMapper: CompressibleAsyncDataTreeNodeMapper = new WeakMapper(node => new CompressibleAsyncDataTreeNodeWrapper(node)); + private filter?: ITreeFilter; constructor( user: string, @@ -1077,6 +1082,7 @@ export class CompressibleAsyncDataTree extends As options: ICompressibleAsyncDataTreeOptions = {} ) { super(user, container, virtualDelegate, renderers, dataSource, options); + this.filter = options.filter; } protected createTree( @@ -1202,4 +1208,34 @@ export class CompressibleAsyncDataTree extends As this.setFocus(focus); } } + + // For compressed async data trees, `TreeVisibility.Recurse` doesn't currently work + // and we have to filter everything beforehand + // Related to #85193 and #85835 + protected processChildren(children: T[]): T[] { + if (this.filter) { + children = children.filter(e => { + const result = this.filter!.filter(e, TreeVisibility.Visible); + const visibility = getVisibility(result); + + if (visibility === TreeVisibility.Recurse) { + throw new Error('Recursive tree visibility not supported in async data compressed trees'); + } + + return visibility === TreeVisibility.Visible; + }); + } + + return super.processChildren(children); + } +} + +function getVisibility(filterResult: TreeFilterResult): TreeVisibility { + if (typeof filterResult === 'boolean') { + return filterResult ? TreeVisibility.Visible : TreeVisibility.Hidden; + } else if (isFilterResult(filterResult)) { + return getVisibleState(filterResult.visibility); + } else { + return getVisibleState(filterResult); + } } From 3295e51f222b99638588a536a4dcff666d8b234d Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Sat, 30 Nov 2019 13:41:40 +0100 Subject: [PATCH 024/255] Make extension respect the window.openFoldersInNewWindow setting. Fixes #85851 --- .../electron/electron-main/electronMainService.ts | 1 + src/vs/platform/windows/common/windows.ts | 1 + src/vs/workbench/api/node/extHostCLIServer.ts | 9 ++------- .../services/host/browser/browserHostService.ts | 2 +- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/vs/platform/electron/electron-main/electronMainService.ts b/src/vs/platform/electron/electron-main/electronMainService.ts index 7c2a6840f9e..24ef0a029ab 100644 --- a/src/vs/platform/electron/electron-main/electronMainService.ts +++ b/src/vs/platform/electron/electron-main/electronMainService.ts @@ -99,6 +99,7 @@ export class ElectronMainService implements IElectronMainService { cli: this.environmentService.args, forceNewWindow: options.forceNewWindow, forceReuseWindow: options.forceReuseWindow, + preferNewWindow: options.preferNewWindow, diffMode: options.diffMode, addMode: options.addMode, gotoLineMode: options.gotoLineMode, diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index 442167fb954..6fbb32cbd7a 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -25,6 +25,7 @@ export interface IBaseOpenWindowsOptions { export interface IOpenWindowOptions extends IBaseOpenWindowsOptions { forceNewWindow?: boolean; + preferNewWindow?: boolean; noRecentEntry?: boolean; } diff --git a/src/vs/workbench/api/node/extHostCLIServer.ts b/src/vs/workbench/api/node/extHostCLIServer.ts index e5d3c44e63b..1ccb71e52a9 100644 --- a/src/vs/workbench/api/node/extHostCLIServer.ts +++ b/src/vs/workbench/api/node/extHostCLIServer.ts @@ -101,9 +101,6 @@ export class CLIServer { for (const s of folderURIs) { try { urisToOpen.push({ folderUri: URI.parse(s) }); - if (!addMode && !forceReuseWindow) { - forceNewWindow = true; - } } catch (e) { // ignore } @@ -114,9 +111,6 @@ export class CLIServer { try { if (hasWorkspaceFileExtension(s)) { urisToOpen.push({ workspaceUri: URI.parse(s) }); - if (!forceReuseWindow) { - forceNewWindow = true; - } } else { urisToOpen.push({ fileUri: URI.parse(s) }); } @@ -127,7 +121,8 @@ export class CLIServer { } if (urisToOpen.length) { const waitMarkerFileURI = waitMarkerFilePath ? URI.file(waitMarkerFilePath) : undefined; - const windowOpenArgs: INativeOpenWindowOptions = { forceNewWindow, diffMode, addMode, gotoLineMode, forceReuseWindow, waitMarkerFileURI }; + const preferNewWindow = !forceReuseWindow && !waitMarkerFileURI && !addMode; + const windowOpenArgs: INativeOpenWindowOptions = { forceNewWindow, diffMode, addMode, gotoLineMode, forceReuseWindow, preferNewWindow, waitMarkerFileURI }; this._commands.executeCommand('_files.windowOpen', urisToOpen, windowOpenArgs); } res.writeHead(200); diff --git a/src/vs/workbench/services/host/browser/browserHostService.ts b/src/vs/workbench/services/host/browser/browserHostService.ts index 4ed69ee646b..dfb857d8350 100644 --- a/src/vs/workbench/services/host/browser/browserHostService.ts +++ b/src/vs/workbench/services/host/browser/browserHostService.ts @@ -146,7 +146,7 @@ export class BrowserHostService extends Disposable implements IHostService { const windowConfig = this.configurationService.getValue('window'); const openFolderInNewWindowConfig = windowConfig?.openFoldersInNewWindow || 'default' /* default */; - let openFolderInNewWindow = !!options.forceNewWindow && !options.forceReuseWindow; + let openFolderInNewWindow = (options.preferNewWindow || !!options.forceNewWindow) && !options.forceReuseWindow; if (!options.forceNewWindow && !options.forceReuseWindow && (openFolderInNewWindowConfig === 'on' || openFolderInNewWindowConfig === 'off')) { openFolderInNewWindow = (openFolderInNewWindowConfig === 'on'); } From f667462a2a8a12c92dbcdd8acf92df7354063691 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Sun, 1 Dec 2019 15:06:55 +0100 Subject: [PATCH 025/255] Opening remote file from cli is opening folder. Fixes #85883 --- .../windows/electron-main/windowsMainService.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index fdf7f2e890b..58183df6c73 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as fs from 'fs'; -import { basename, normalize, join, } from 'vs/base/common/path'; +import { basename, normalize, join, posix } from 'vs/base/common/path'; import { localize } from 'vs/nls'; import * as arrays from 'vs/base/common/arrays'; import { assign, mixin } from 'vs/base/common/objects'; @@ -1108,8 +1108,6 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic const remoteAuthority = options.remoteAuthority; if (remoteAuthority) { - // assume it's a folder or workspace file - const first = anyPath.charCodeAt(0); // make absolute if (first !== CharCode.Slash) { @@ -1121,11 +1119,15 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic const uri = URI.from({ scheme: Schemas.vscodeRemote, authority: remoteAuthority, path: anyPath }); - if (hasWorkspaceFileExtension(anyPath)) { - if (forceOpenWorkspaceAsFile) { + // guess the file type: If it ends with a slash it's a folder. If it has a file extension, it's a file or a workspace. By defaults it's a folder. + if (anyPath.charCodeAt(anyPath.length - 1) !== CharCode.Slash) { + if (hasWorkspaceFileExtension(anyPath)) { + if (forceOpenWorkspaceAsFile) { + return { fileUri: uri, remoteAuthority }; + } + } else if (posix.extname(anyPath).length > 0) { return { fileUri: uri, remoteAuthority }; } - return { workspace: getWorkspaceIdentifier(uri), remoteAuthority }; } return { folderUri: uri, remoteAuthority }; } From dd15bedae729941166a5806f71195a05ded14a0d Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 22 Nov 2019 17:03:33 +0100 Subject: [PATCH 026/255] implement keybindings sync and merge straregy --- .../userDataSync/common/keybindingsSync.ts | 122 ++++++ .../userDataSync/common/settingsSync.ts | 4 +- .../userDataSync/common/userDataSync.ts | 10 + .../keybinding/common/keybindingsMerge.ts | 292 ++++++++++++++ .../keybindingEditing.test.ts | 2 +- .../electron-browser/keybindingsMerge.test.ts | 377 ++++++++++++++++++ 6 files changed, 804 insertions(+), 3 deletions(-) create mode 100644 src/vs/platform/userDataSync/common/keybindingsSync.ts create mode 100644 src/vs/workbench/services/keybinding/common/keybindingsMerge.ts create mode 100644 src/vs/workbench/services/keybinding/test/electron-browser/keybindingsMerge.test.ts diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts new file mode 100644 index 00000000000..b35731feb4d --- /dev/null +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -0,0 +1,122 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import { IFileService, FileSystemProviderErrorCode, FileSystemProviderError, IFileContent } from 'vs/platform/files/common/files'; +import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, ISynchroniser, SyncStatus, ISettingsMergeService, IUserDataSyncStoreService, DEFAULT_IGNORED_SETTINGS, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { parse, ParseError } from 'vs/base/common/json'; +import { localize } from 'vs/nls'; +import { Emitter, Event } from 'vs/base/common/event'; +import { CancelablePromise, createCancelablePromise, ThrottledDelayer } from 'vs/base/common/async'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { URI } from 'vs/base/common/uri'; +import { joinPath } from 'vs/base/common/resources'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { startsWith } from 'vs/base/common/strings'; +import { CancellationToken } from 'vs/base/common/cancellation'; + + +export class KeybindingsSynchroniser extends Disposable implements ISynchroniser { + + private static EXTERNAL_USER_DATA_KEYBINDINGS_KEY: string = 'keybindings'; + + private _status: SyncStatus = SyncStatus.Idle; + get status(): SyncStatus { return this._status; } + private _onDidChangStatus: Emitter = this._register(new Emitter()); + readonly onDidChangeStatus: Event = this._onDidChangStatus.event; + + private readonly throttledDelayer: ThrottledDelayer; + private _onDidChangeLocal: Emitter = this._register(new Emitter()); + readonly onDidChangeLocal: Event = this._onDidChangeLocal.event; + + private readonly lastSyncKeybindingsResource: URI; + + constructor( + @IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService, + @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IFileService private readonly fileService: IFileService, + @IEnvironmentService private readonly environmentService: IEnvironmentService, + ) { + super(); + this.lastSyncKeybindingsResource = joinPath(this.environmentService.userRoamingDataHome, '.lastSyncKeybindings.json'); + this.throttledDelayer = this._register(new ThrottledDelayer(500)); + this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.environmentService.keybindingsResource))(() => this.throttledDelayer.trigger(() => this.onDidChangeKeybindings()))); + } + + private async onDidChangeKeybindings(): Promise { + const localFileContent = await this.getLocalFileContent(); + const lastSyncData = await this.getLastSyncUserData(); + if (localFileContent && lastSyncData) { + if (localFileContent.value.toString() !== lastSyncData.content) { + this._onDidChangeLocal.fire(); + return; + } + } + if (!localFileContent || !lastSyncData) { + this._onDidChangeLocal.fire(); + return; + } + } + + private setStatus(status: SyncStatus): void { + if (this._status !== status) { + this._status = status; + this._onDidChangStatus.fire(status); + } + } + + async sync(): Promise { + if (!this.configurationService.getValue('sync.enableKeybindings')) { + this.logService.trace('Keybindings: Skipping synchronizing keybindings as it is disabled.'); + return false; + } + + if (this.status !== SyncStatus.Idle) { + this.logService.trace('Keybindings: Skipping synchronizing keybindings as it is running already.'); + return false; + } + + this.logService.trace('Keybindings: Started synchronizing keybindings...'); + this.setStatus(SyncStatus.Syncing); + + } + + private async getLastSyncUserData(): Promise { + try { + const content = await this.fileService.readFile(this.lastSyncKeybindingsResource); + return JSON.parse(content.value.toString()); + } catch (error) { + return null; + } + } + + private async getLocalFileContent(): Promise { + try { + return await this.fileService.readFile(this.environmentService.settingsResource); + } catch (error) { + return null; + } + } + + private async writeToRemote(content: string, ref: string | null): Promise { + return this.userDataSyncStoreService.write(KeybindingsSynchroniser.EXTERNAL_USER_DATA_KEYBINDINGS_KEY, content, ref); + } + + private async writeToLocal(newContent: string, oldContent: IFileContent | null): Promise { + if (oldContent) { + // file exists already + await this.fileService.writeFile(this.environmentService.settingsResource, VSBuffer.fromString(newContent), oldContent); + } else { + // file does not exist + await this.fileService.createFile(this.environmentService.settingsResource, VSBuffer.fromString(newContent), { overwrite: false }); + } + } + + private async updateLastSyncValue(remoteUserData: IUserData): Promise { + await this.fileService.writeFile(this.lastSyncSettingsResource, VSBuffer.fromString(JSON.stringify(remoteUserData))); + } +} diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index 1a757524dd6..96636df1a35 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -218,8 +218,8 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { } if (!lastSyncData // First time sync - || lastSyncData.content !== localContent // Local has moved forwarded - || lastSyncData.content !== remoteContent // Remote has moved forwarded + || lastSyncData.content !== localContent // Local has forwarded + || lastSyncData.content !== remoteContent // Remote has forwarded ) { this.logService.trace('Settings: Merging remote settings with local settings...'); const result = await this.settingsMergeService.merge(localContent, remoteContent, lastSyncData ? lastSyncData.content : null, this.getIgnoredSettings()); diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index 5771808a1c9..3c0598d5e17 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -173,6 +173,16 @@ export interface ISettingsMergeService { } +export const IKeybindingsMergeService = createDecorator('IKeybindingsMergeService'); + +export interface IKeybindingsMergeService { + + _serviceBrand: undefined; + + merge(localContent: string, remoteContent: string, baseContent: string | null): Promise<{ mergeContent: string, hasChanges: boolean, hasConflicts: boolean }>; + +} + export const IUserDataSyncLogService = createDecorator('IUserDataSyncLogService'); export interface IUserDataSyncLogService extends ILogService { diff --git a/src/vs/workbench/services/keybinding/common/keybindingsMerge.ts b/src/vs/workbench/services/keybinding/common/keybindingsMerge.ts new file mode 100644 index 00000000000..ae03545d81d --- /dev/null +++ b/src/vs/workbench/services/keybinding/common/keybindingsMerge.ts @@ -0,0 +1,292 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as objects from 'vs/base/common/objects'; +import { parse, findNodeAtLocation, parseTree, JSONPath } from 'vs/base/common/json'; +import { EditOperation } from 'vs/editor/common/core/editOperation'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { ITextModel } from 'vs/editor/common/model'; +import { setProperty } from 'vs/base/common/jsonEdit'; +import { Range } from 'vs/editor/common/core/range'; +import { Selection } from 'vs/editor/common/core/selection'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { Position } from 'vs/editor/common/core/position'; +import { values, keys } from 'vs/base/common/map'; +import { IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding'; +import { firstIndex, equals } from 'vs/base/common/arrays'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IKeybindingsMergeService } from 'vs/platform/userDataSync/common/userDataSync'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; + +export class KeybindingsMergeService implements IKeybindingsMergeService { + + _serviceBrand: undefined; + + constructor( + @IModelService private readonly modelService: IModelService, + @IModeService private readonly modeService: IModeService, + ) { } + + async merge(localContent: string, remoteContent: string, baseContent: string | null): Promise<{ mergeContent: string, hasChanges: boolean, hasConflicts: boolean }> { + const local = parse(localContent); + const remote = parse(remoteContent); + const base = baseContent ? parse(baseContent) : null; + + const byCommand = (keybindings: IUserFriendlyKeybinding[]) => { + const map: Map = new Map(); + for (const keybinding of keybindings) { + const command = keybinding.command[0] === '-' ? keybinding.command.substring(1) : keybinding.command; + let value = map.get(command); + if (!value) { + value = []; + map.set(command, value); + } + value.push(keybinding); + } + return map; + }; + + const localByCommand = byCommand(local); + const remoteByCommand = byCommand(remote); + const baseByCommand = base ? byCommand(base) : null; + + const localToRemote = this.compare(localByCommand, remoteByCommand); + if (localToRemote.added.size === 0 && localToRemote.removed.size === 0 && localToRemote.updated.size === 0) { + // No changes found between local and remote. + return { mergeContent: localContent, hasChanges: false, hasConflicts: false }; + } + + const conflictCommands: Set = new Set(); + const baseToLocal = baseByCommand ? this.compare(baseByCommand, localByCommand) : { added: keys(localByCommand).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; + const baseToRemote = baseByCommand ? this.compare(baseByCommand, remoteByCommand) : { added: keys(remoteByCommand).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; + const keybindingsPreviewModel = this.modelService.createModel(localContent, this.modeService.create('jsonc')); + + // Removed commands in Local + for (const command of values(baseToLocal.removed)) { + // Got updated in remote + if (baseToRemote.updated.has(command)) { + conflictCommands.add(command); + } + } + + // Removed commands in Remote + for (const command of values(baseToRemote.removed)) { + if (conflictCommands.has(command)) { + continue; + } + // Got updated in local + if (baseToLocal.updated.has(command)) { + conflictCommands.add(command); + } else { + // remove the command + this.removeKeybindings(keybindingsPreviewModel, command); + } + } + + // Added commands in Local + for (const command of values(baseToLocal.added)) { + if (conflictCommands.has(command)) { + continue; + } + // Got added in remote + if (baseToRemote.added.has(command)) { + // Has different value + if (localToRemote.updated.has(command)) { + conflictCommands.add(command); + } + } + } + + // Added commands in remote + for (const command of values(baseToRemote.added)) { + if (conflictCommands.has(command)) { + continue; + } + // Got added in local + if (baseToLocal.added.has(command)) { + // Has different value + if (localToRemote.updated.has(command)) { + conflictCommands.add(command); + } + } else { + this.addKeybinding(keybindingsPreviewModel, command, remoteByCommand.get(command)!); + } + } + + // Updated commands in Local + for (const command of values(baseToLocal.updated)) { + if (conflictCommands.has(command)) { + continue; + } + // Got updated in remote + if (baseToRemote.updated.has(command)) { + // Has different value + if (localToRemote.updated.has(command)) { + conflictCommands.add(command); + } + } + } + + // Updated commands in Remote + for (const command of values(baseToRemote.updated)) { + if (conflictCommands.has(command)) { + continue; + } + // Got updated in local + if (baseToLocal.updated.has(command)) { + // Has different value + if (localToRemote.updated.has(command)) { + conflictCommands.add(command); + } + } else { + // update the command + this.updateKeybinding(keybindingsPreviewModel, command, remoteByCommand.get(command)!); + } + } + + const conflicts: { index: number, command: string }[] = []; + const previewKeybindings = parse(keybindingsPreviewModel.getValue()); + for (const command of values(conflictCommands)) { + const index = firstIndex(previewKeybindings, keybinding => keybinding.command === command || keybinding.command === `-${command}`); + if (index > 0) { + conflicts.push({ command, index }); + } + } + conflicts.sort((a, b) => b.index - a.index); + + // Move all entries with same command together + for (const conflict of conflicts) { + this.updateKeybinding(keybindingsPreviewModel, conflict.command, localByCommand.get(conflict.command)!); + } + + const eol = keybindingsPreviewModel.getEOL(); + for (const conflict of conflicts) { + const tree = parseTree(keybindingsPreviewModel.getValue()); + const valueNode = findNodeAtLocation(tree, [conflict.index]); + const remoteEdit = setProperty(`[${eol}\t${eol}]`, [-1], remoteByCommand.get(conflict.command), { tabSize: 4, insertSpaces: false, eol: eol })[0]; + const remoteContent = remoteEdit ? `${remoteEdit.content.substring(remoteEdit.offset + remoteEdit.length + 1)},${eol}` : ''; + if (valueNode) { + // Updated in Local and Remote with different value + const valueStartPosition = keybindingsPreviewModel.getPositionAt(valueNode.offset); + const valueEndPosition = keybindingsPreviewModel.getPositionAt(valueNode.offset + valueNode.length); + const editOperations = [ + EditOperation.insert(new Position(valueStartPosition.lineNumber - 1, keybindingsPreviewModel.getLineMaxColumn(valueStartPosition.lineNumber - 1)), `${eol}<<<<<<< local`), + EditOperation.insert(new Position(valueEndPosition.lineNumber, keybindingsPreviewModel.getLineMaxColumn(valueEndPosition.lineNumber)), `${eol}=======${eol}${remoteContent}>>>>>>> remote`) + ]; + keybindingsPreviewModel.pushEditOperations([new Selection(valueStartPosition.lineNumber, valueStartPosition.column, valueStartPosition.lineNumber, valueStartPosition.column)], editOperations, () => []); + } else { + // Removed in Local, but updated in Remote + const position = new Position(keybindingsPreviewModel.getLineCount() - 1, keybindingsPreviewModel.getLineMaxColumn(keybindingsPreviewModel.getLineCount() - 1)); + const editOperations = [ + EditOperation.insert(position, `${eol}<<<<<<< local${eol}=======${eol}${remoteContent}>>>>>>> remote`) + ]; + keybindingsPreviewModel.pushEditOperations([new Selection(position.lineNumber, position.column, position.lineNumber, position.column)], editOperations, () => []); + } + } + + return { mergeContent: keybindingsPreviewModel.getValue(), hasChanges: true, hasConflicts: conflicts.length > 0 }; + } + + private compare(from: Map, to: Map): { added: Set, removed: Set, updated: Set } { + const fromKeys = keys(from); + const toKeys = keys(to); + const added = toKeys.filter(key => fromKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); + const removed = fromKeys.filter(key => toKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); + const updated: Set = new Set(); + + for (const key of fromKeys) { + if (removed.has(key)) { + continue; + } + const value1: IUserFriendlyKeybinding[] = from.get(key)!; + const value2: IUserFriendlyKeybinding[] = to.get(key)!; + if (!this.areSameKeybindings(value1, value2)) { + updated.add(key); + } + } + + return { added, removed, updated }; + } + + private areSameKeybindings(value1: IUserFriendlyKeybinding[], value2: IUserFriendlyKeybinding[]): boolean { + // Compare entries adding keybindings + if (!equals(value1.filter(({ command }) => command[0] !== '-'), value2.filter(({ command }) => command[0] !== '-'), (a, b) => this.isSameKeybinding(a, b))) { + return false; + } + // Compare entries removing keybindings + if (!equals(value1.filter(({ command }) => command[0] === '-'), value2.filter(({ command }) => command[0] === '-'), (a, b) => this.isSameKeybinding(a, b))) { + return false; + } + return true; + } + + private isSameKeybinding(a: IUserFriendlyKeybinding, b: IUserFriendlyKeybinding): boolean { + if (a.command !== b.command) { + return false; + } + if (a.key !== b.key) { + return false; + } + const whenA = ContextKeyExpr.deserialize(a.when); + const whenB = ContextKeyExpr.deserialize(b.when); + if ((whenA && !whenB) || (!whenA && whenB)) { + return false; + } + if (whenA && whenB && !whenA.equals(whenB)) { + return false; + } + if (!objects.equals(a.args, b.args)) { + return false; + } + return true; + } + + private addKeybinding(model: ITextModel, command: string, keybindings: IUserFriendlyKeybinding[]): void { + for (const keybinding of keybindings) { + this.edit(model, [-1], keybinding); + } + } + + private removeKeybindings(model: ITextModel, command: string): void { + const keybindings = parse(model.getValue()); + for (let index = keybindings.length - 1; index >= 0; index--) { + if (keybindings[index].command === command || keybindings[index].command === `-${command}`) { + this.edit(model, [index], undefined); + } + } + } + + private updateKeybinding(model: ITextModel, command: string, keybindings: IUserFriendlyKeybinding[]): void { + const allKeybindings = parse(model.getValue()); + const location = firstIndex(allKeybindings, keybinding => keybinding.command === command || keybinding.command === `-${command}`); + // Remove all entries with this command + for (let index = allKeybindings.length - 1; index >= 0; index--) { + if (allKeybindings[index].command === command || allKeybindings[index].command === `-${command}`) { + this.edit(model, [index], undefined); + } + } + // add all entries at the same location where the entry with this command was located. + for (let index = keybindings.length - 1; index >= 0; index--) { + this.edit(model, [location], keybindings[index]); + } + } + + private edit(model: ITextModel, originalPath: JSONPath, value: any) { + const edit = setProperty(model.getValue(), originalPath, value, { tabSize: 4, insertSpaces: false, eol: model.getEOL() })[0]; + if (edit) { + const startPosition = model.getPositionAt(edit.offset); + const endPosition = model.getPositionAt(edit.offset + edit.length); + const range = new Range(startPosition.lineNumber, startPosition.column, endPosition.lineNumber, endPosition.column); + let currentText = model.getValueInRange(range); + if (edit.content !== currentText) { + const editOperation = currentText ? EditOperation.replace(range, edit.content) : EditOperation.insert(startPosition, edit.content); + model.pushEditOperations([new Selection(startPosition.lineNumber, startPosition.column, startPosition.lineNumber, startPosition.column)], [editOperation], () => []); + } + } + } + +} + +registerSingleton(IKeybindingsMergeService, KeybindingsMergeService); diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts index 26baa78b0ab..8b62fa9c23f 100644 --- a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts +++ b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts @@ -68,7 +68,7 @@ interface Modifiers { shiftKey?: boolean; } -suite.skip('KeybindingsEditing', () => { +suite('KeybindingsEditing', () => { let instantiationService: TestInstantiationService; let testObject: KeybindingsEditingService; diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingsMerge.test.ts b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingsMerge.test.ts new file mode 100644 index 00000000000..a1e5c53776a --- /dev/null +++ b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingsMerge.test.ts @@ -0,0 +1,377 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { workbenchInstantiationService } from 'vs/workbench/test/workbenchTestServices'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { KeybindingsMergeService } from 'vs/workbench/services/keybinding/common/keybindingsMerge'; + +suite('KeybindingsMerge', () => { + + let instantiationService: IInstantiationService; + let testObject: KeybindingsMergeService; + + setup(() => { + instantiationService = workbenchInstantiationService(); + testObject = instantiationService.createInstance(KeybindingsMergeService); + }); + + test('merge when local and remote are same with one entry', async () => { + const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const actual = await testObject.merge(localContent, remoteContent, null); + assert.ok(!actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, localContent); + }); + + test('merge when local and remote are same with similar when contexts', async () => { + const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: '!editorReadonly && editorTextFocus' }]); + const actual = await testObject.merge(localContent, remoteContent, null); + assert.ok(!actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, localContent); + }); + + test('merge when local and remote are same with multiple entries', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } } + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } } + ]); + const actual = await testObject.merge(localContent, remoteContent, null); + assert.ok(!actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, localContent); + }); + + test('merge when local and remote are same with different base content', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } } + ]); + const baseContent = stringify([ + { key: 'ctrl+c', command: 'e' }, + { key: 'shift+d', command: 'd', args: { text: '`' } } + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } } + ]); + const actual = await testObject.merge(localContent, remoteContent, baseContent); + assert.ok(!actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, localContent); + }); + + test('merge when local and remote are same with multiple entries in different order', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } } + ]); + const remoteContent = stringify([ + { key: 'cmd+c', command: 'b', args: { text: '`' } }, + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + ]); + const actual = await testObject.merge(localContent, remoteContent, null); + assert.ok(!actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, localContent); + }); + + test('merge when local and remote are same when remove entry is in different order', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } } + ]); + const remoteContent = stringify([ + { key: 'alt+d', command: '-a' }, + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } }, + ]); + const actual = await testObject.merge(localContent, remoteContent, null); + assert.ok(!actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, localContent); + }); + + test('merge when a new entry is added to remote', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } }, + ]); + const actual = await testObject.merge(localContent, remoteContent, null); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, remoteContent); + }); + + test('merge when multiple new entries are added to remote', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } }, + { key: 'cmd+d', command: 'c' }, + ]); + const actual = await testObject.merge(localContent, remoteContent, null); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, remoteContent); + }); + + test('merge when multiple new entries are added to remote from base and local has not changed', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } }, + { key: 'cmd+d', command: 'c' }, + ]); + const actual = await testObject.merge(localContent, remoteContent, localContent); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, remoteContent); + }); + + test('merge when an entry is removed from remote from base and local has not changed', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } }, + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + ]); + const actual = await testObject.merge(localContent, remoteContent, localContent); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, remoteContent); + }); + + test('merge when an entry (same command) is removed from remote from base and local has not changed', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + ]); + const actual = await testObject.merge(localContent, remoteContent, localContent); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, remoteContent); + }); + + test('merge when an entry is updated in remote from base and local has not changed', async () => { + const localContent = stringify([ + { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + ]); + const actual = await testObject.merge(localContent, remoteContent, localContent); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, remoteContent); + }); + + test('merge when a command with multiple entries is updated from remote from base and local has not changed', async () => { + const localContent = stringify([ + { key: 'shift+c', command: 'c' }, + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: 'b' }, + { key: 'cmd+c', command: 'a' }, + ]); + const remoteContent = stringify([ + { key: 'shift+c', command: 'c' }, + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: 'b' }, + { key: 'cmd+d', command: 'a' }, + ]); + const expected = stringify([ + { key: 'shift+c', command: 'c' }, + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'cmd+d', command: 'a' }, + { key: 'alt+d', command: 'b' }, + ]); + const actual = await testObject.merge(localContent, remoteContent, localContent); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, expected); + }); + + test('merge when remote has moved forwareded with multiple changes and local stays with base', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+e', command: 'd' }, + { key: 'cmd+d', command: 'c', when: 'context1' }, + ]); + const remoteContent = stringify([ + { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'cmd+e', command: 'd' }, + { key: 'alt+d', command: '-a' }, + { key: 'alt+f', command: 'f' }, + { key: 'alt+d', command: '-f' }, + { key: 'cmd+d', command: 'c', when: 'context1' }, + { key: 'cmd+c', command: '-c' }, + ]); + const expected = stringify([ + { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+e', command: 'd' }, + { key: 'alt+f', command: 'f' }, + { key: 'alt+d', command: '-f' }, + { key: 'cmd+d', command: 'c', when: 'context1' }, + { key: 'cmd+c', command: '-c' }, + ]); + const actual = await testObject.merge(localContent, remoteContent, localContent); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, expected); + }); + + test('merge when a new entry is added to local', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } }, + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + ]); + const actual = await testObject.merge(localContent, remoteContent, null); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, localContent); + }); + + test('merge when multiple new entries are added to local', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } }, + { key: 'cmd+d', command: 'c' }, + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + ]); + const actual = await testObject.merge(localContent, remoteContent, null); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, localContent); + }); + + test('merge when multiple new entries are added to local from base and remote is not changed', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } }, + { key: 'cmd+d', command: 'c' }, + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + ]); + const actual = await testObject.merge(localContent, remoteContent, remoteContent); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, localContent); + }); + + test('merge when an entry is removed from local from base and remote has not changed', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } }, + ]); + const actual = await testObject.merge(localContent, remoteContent, remoteContent); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, localContent); + }); + + test('merge when an entry (with same command) is removed from local from base and remote has not changed', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + ]); + const actual = await testObject.merge(localContent, remoteContent, remoteContent); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, localContent); + }); + + test('merge when an entry is updated in local from base and remote has not changed', async () => { + const localContent = stringify([ + { key: 'alt+d', command: 'a', when: 'editorTextFocus' }, + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + ]); + const actual = await testObject.merge(localContent, remoteContent, remoteContent); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, localContent); + }); + + test('merge when a command with multiple entries is updated from local from base and remote has not changed', async () => { + const localContent = stringify([ + { key: 'shift+c', command: 'c' }, + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: 'b' }, + { key: 'cmd+c', command: 'a' }, + ]); + const remoteContent = stringify([ + { key: 'shift+c', command: 'c' }, + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: 'b' }, + { key: 'cmd+d', command: 'a' }, + ]); + const actual = await testObject.merge(localContent, remoteContent, remoteContent); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, localContent); + }); + + function stringify(value: any): any { + return JSON.stringify(value, null, '\t'); + } + +}); From 89ccd4a1da580e8780f9060b0ba456d41e46d2df Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 22 Nov 2019 20:19:07 +0100 Subject: [PATCH 027/255] add more tests --- .../electron-browser/keybindingsMerge.test.ts | 76 ++++++++++++++++++- 1 file changed, 74 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingsMerge.test.ts b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingsMerge.test.ts index a1e5c53776a..fee48701771 100644 --- a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingsMerge.test.ts +++ b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingsMerge.test.ts @@ -247,10 +247,10 @@ suite('KeybindingsMerge', () => { { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, { key: 'cmd+e', command: 'd' }, - { key: 'alt+f', command: 'f' }, - { key: 'alt+d', command: '-f' }, { key: 'cmd+d', command: 'c', when: 'context1' }, { key: 'cmd+c', command: '-c' }, + { key: 'alt+f', command: 'f' }, + { key: 'alt+d', command: '-f' }, ]); const actual = await testObject.merge(localContent, remoteContent, localContent); assert.ok(actual.hasChanges); @@ -370,6 +370,78 @@ suite('KeybindingsMerge', () => { assert.equal(actual.mergeContent, localContent); }); + test('merge when local has moved forwareded with multiple changes and remote stays with base', async () => { + const localContent = stringify([ + { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'cmd+e', command: 'd' }, + { key: 'alt+d', command: '-a' }, + { key: 'alt+f', command: 'f' }, + { key: 'alt+d', command: '-f' }, + { key: 'cmd+d', command: 'c', when: 'context1' }, + { key: 'cmd+c', command: '-c' }, + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+e', command: 'd' }, + { key: 'cmd+d', command: 'c', when: 'context1' }, + ]); + const expected = stringify([ + { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'cmd+e', command: 'd' }, + { key: 'alt+d', command: '-a' }, + { key: 'alt+f', command: 'f' }, + { key: 'alt+d', command: '-f' }, + { key: 'cmd+d', command: 'c', when: 'context1' }, + { key: 'cmd+c', command: '-c' }, + ]); + const actual = await testObject.merge(localContent, remoteContent, remoteContent); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, expected); + }); + + test('merge when local and remove has moved forwareded with no conflicts', async () => { + const baseContent = stringify([ + { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+c', command: '-a' }, + { key: 'cmd+e', command: 'd' }, + { key: 'alt+f', command: 'f' }, + { key: 'alt+d', command: '-f' }, + { key: 'cmd+d', command: 'c', when: 'context1' }, + { key: 'cmd+c', command: '-c' }, + ]); + const localContent = stringify([ + { key: 'alt+d', command: '-f' }, + { key: 'cmd+e', command: 'd' }, + { key: 'cmd+c', command: '-c' }, + { key: 'cmd+d', command: 'c', when: 'context1' }, + { key: 'alt+a', command: 'f' }, + { key: 'alt+e', command: 'e' }, + ]); + const remoteContent = stringify([ + { key: 'cmd+c', command: '-c' }, + { key: 'cmd+d', command: 'd' }, + { key: 'alt+d', command: '-f' }, + { key: 'alt+c', command: 'c', when: 'context1' }, + { key: 'alt+g', command: 'g', when: 'context2' }, + ]); + const expected = stringify([ + { key: 'alt+d', command: '-f' }, + { key: 'cmd+d', command: 'd' }, + { key: 'cmd+c', command: '-c' }, + { key: 'alt+c', command: 'c', when: 'context1' }, + { key: 'alt+a', command: 'f' }, + { key: 'alt+e', command: 'e' }, + { key: 'alt+g', command: 'g', when: 'context2' }, + ]); + const actual = await testObject.merge(localContent, remoteContent, baseContent); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, expected); + }); + function stringify(value: any): any { return JSON.stringify(value, null, '\t'); } From 4f4f7ead1155e2d19af09b25dac1c7ebdd2b340e Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 22 Nov 2019 23:18:45 +0100 Subject: [PATCH 028/255] :lipstick: --- .../keybinding/test/electron-browser/keybindingsMerge.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingsMerge.test.ts b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingsMerge.test.ts index fee48701771..44e278bd4c0 100644 --- a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingsMerge.test.ts +++ b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingsMerge.test.ts @@ -402,7 +402,7 @@ suite('KeybindingsMerge', () => { assert.equal(actual.mergeContent, expected); }); - test('merge when local and remove has moved forwareded with no conflicts', async () => { + test('merge when local and remote has moved forwareded with no conflicts', async () => { const baseContent = stringify([ { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+c', command: '-a' }, From b3f1daef2c80df7a9cc9aa2d53edcbed3d12c732 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Sun, 24 Nov 2019 09:37:07 +0100 Subject: [PATCH 029/255] add tests for conflicts --- .../keybinding/common/keybindingsMerge.ts | 14 ++++-- .../electron-browser/keybindingsMerge.test.ts | 43 ++++++++++++++----- 2 files changed, 44 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/services/keybinding/common/keybindingsMerge.ts b/src/vs/workbench/services/keybinding/common/keybindingsMerge.ts index ae03545d81d..b80249778da 100644 --- a/src/vs/workbench/services/keybinding/common/keybindingsMerge.ts +++ b/src/vs/workbench/services/keybinding/common/keybindingsMerge.ts @@ -150,7 +150,7 @@ export class KeybindingsMergeService implements IKeybindingsMergeService { const previewKeybindings = parse(keybindingsPreviewModel.getValue()); for (const command of values(conflictCommands)) { const index = firstIndex(previewKeybindings, keybinding => keybinding.command === command || keybinding.command === `-${command}`); - if (index > 0) { + if (index >= 0) { conflicts.push({ command, index }); } } @@ -165,8 +165,7 @@ export class KeybindingsMergeService implements IKeybindingsMergeService { for (const conflict of conflicts) { const tree = parseTree(keybindingsPreviewModel.getValue()); const valueNode = findNodeAtLocation(tree, [conflict.index]); - const remoteEdit = setProperty(`[${eol}\t${eol}]`, [-1], remoteByCommand.get(conflict.command), { tabSize: 4, insertSpaces: false, eol: eol })[0]; - const remoteContent = remoteEdit ? `${remoteEdit.content.substring(remoteEdit.offset + remoteEdit.length + 1)},${eol}` : ''; + const remoteContent = this.getRemoteContentForConflict(eol, remoteByCommand.get(conflict.command)!); if (valueNode) { // Updated in Local and Remote with different value const valueStartPosition = keybindingsPreviewModel.getPositionAt(valueNode.offset); @@ -189,6 +188,15 @@ export class KeybindingsMergeService implements IKeybindingsMergeService { return { mergeContent: keybindingsPreviewModel.getValue(), hasChanges: true, hasConflicts: conflicts.length > 0 }; } + private getRemoteContentForConflict(eol: string, keybindings: IUserFriendlyKeybinding[]): string { + let content = `[${eol}${eol}]`; + for (const keybinding of keybindings) { + const edit = setProperty(content, [-1], keybinding, { tabSize: 4, insertSpaces: false, eol })[0]; + content = content.substring(0, edit.offset) + edit.content + content.substring(edit.offset + edit.length); + } + return content.substring(2, content.length - 2); + } + private compare(from: Map, to: Map): { added: Set, removed: Set, updated: Set } { const fromKeys = keys(from); const toKeys = keys(to); diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingsMerge.test.ts b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingsMerge.test.ts index 44e278bd4c0..0399e01b4f2 100644 --- a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingsMerge.test.ts +++ b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingsMerge.test.ts @@ -5,18 +5,13 @@ import * as assert from 'assert'; import { workbenchInstantiationService } from 'vs/workbench/test/workbenchTestServices'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingsMergeService } from 'vs/workbench/services/keybinding/common/keybindingsMerge'; -suite('KeybindingsMerge', () => { +let testObject: KeybindingsMergeService; - let instantiationService: IInstantiationService; - let testObject: KeybindingsMergeService; +suiteSetup(() => testObject = workbenchInstantiationService().createInstance(KeybindingsMergeService)); - setup(() => { - instantiationService = workbenchInstantiationService(); - testObject = instantiationService.createInstance(KeybindingsMergeService); - }); +suite('KeybindingsMerge - No Conflicts', () => { test('merge when local and remote are same with one entry', async () => { const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); @@ -442,8 +437,36 @@ suite('KeybindingsMerge', () => { assert.equal(actual.mergeContent, expected); }); - function stringify(value: any): any { - return JSON.stringify(value, null, '\t'); +}); + +suite('KeybindingsMerge - Conflicts', () => { + + test('merge when local and remote with one entry but different value', async () => { + const localContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const actual = await testObject.merge(localContent, remoteContent, null); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `[ +<<<<<<< local + { + "key": "alt+d", + "command": "a", + "when": "editorTextFocus && !editorReadonly" } +======= + { + "key": "alt+c", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + } +>>>>>>> remote +]`); + }); }); + +function stringify(value: any): any { + return JSON.stringify(value, null, '\t'); +} From 0243b7f9fc8180e54a333cbf4d7bf575fcf5d8d8 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 25 Nov 2019 00:28:28 +0100 Subject: [PATCH 030/255] Refactor merge conflicts computation --- .../keybinding/common/keybindingsMerge.ts | 131 ++++++++---------- .../electron-browser/keybindingsMerge.test.ts | 127 ++++++++++++++++- 2 files changed, 185 insertions(+), 73 deletions(-) diff --git a/src/vs/workbench/services/keybinding/common/keybindingsMerge.ts b/src/vs/workbench/services/keybinding/common/keybindingsMerge.ts index b80249778da..006fd5005dd 100644 --- a/src/vs/workbench/services/keybinding/common/keybindingsMerge.ts +++ b/src/vs/workbench/services/keybinding/common/keybindingsMerge.ts @@ -5,17 +5,12 @@ import * as objects from 'vs/base/common/objects'; import { parse, findNodeAtLocation, parseTree, JSONPath } from 'vs/base/common/json'; -import { EditOperation } from 'vs/editor/common/core/editOperation'; -import { IModeService } from 'vs/editor/common/services/modeService'; -import { ITextModel } from 'vs/editor/common/model'; import { setProperty } from 'vs/base/common/jsonEdit'; -import { Range } from 'vs/editor/common/core/range'; -import { Selection } from 'vs/editor/common/core/selection'; import { IModelService } from 'vs/editor/common/services/modelService'; import { Position } from 'vs/editor/common/core/position'; import { values, keys } from 'vs/base/common/map'; import { IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding'; -import { firstIndex, equals } from 'vs/base/common/arrays'; +import { firstIndex as findFirstIndex, equals } from 'vs/base/common/arrays'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IKeybindingsMergeService } from 'vs/platform/userDataSync/common/userDataSync'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -25,8 +20,7 @@ export class KeybindingsMergeService implements IKeybindingsMergeService { _serviceBrand: undefined; constructor( - @IModelService private readonly modelService: IModelService, - @IModeService private readonly modeService: IModeService, + @IModelService private readonly modelService: IModelService ) { } async merge(localContent: string, remoteContent: string, baseContent: string | null): Promise<{ mergeContent: string, hasChanges: boolean, hasConflicts: boolean }> { @@ -61,7 +55,8 @@ export class KeybindingsMergeService implements IKeybindingsMergeService { const conflictCommands: Set = new Set(); const baseToLocal = baseByCommand ? this.compare(baseByCommand, localByCommand) : { added: keys(localByCommand).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; const baseToRemote = baseByCommand ? this.compare(baseByCommand, remoteByCommand) : { added: keys(remoteByCommand).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; - const keybindingsPreviewModel = this.modelService.createModel(localContent, this.modeService.create('jsonc')); + let mergeContent = localContent; + const eol = this.modelService.createModel(mergeContent, null).getEOL(); // Removed commands in Local for (const command of values(baseToLocal.removed)) { @@ -81,7 +76,7 @@ export class KeybindingsMergeService implements IKeybindingsMergeService { conflictCommands.add(command); } else { // remove the command - this.removeKeybindings(keybindingsPreviewModel, command); + mergeContent = this.removeKeybindings(mergeContent, eol, command); } } @@ -111,7 +106,7 @@ export class KeybindingsMergeService implements IKeybindingsMergeService { conflictCommands.add(command); } } else { - this.addKeybinding(keybindingsPreviewModel, command, remoteByCommand.get(command)!); + mergeContent = this.addKeybinding(mergeContent, eol, command, remoteByCommand.get(command)!); } } @@ -142,59 +137,54 @@ export class KeybindingsMergeService implements IKeybindingsMergeService { } } else { // update the command - this.updateKeybinding(keybindingsPreviewModel, command, remoteByCommand.get(command)!); + mergeContent = this.updateKeybinding(mergeContent, eol, command, remoteByCommand.get(command)!); } } - const conflicts: { index: number, command: string }[] = []; - const previewKeybindings = parse(keybindingsPreviewModel.getValue()); + const conflicts: { command: string, local: IUserFriendlyKeybinding[] | undefined, remote: IUserFriendlyKeybinding[] | undefined, firstIndex: number }[] = []; + for (const command of values(conflictCommands)) { - const index = firstIndex(previewKeybindings, keybinding => keybinding.command === command || keybinding.command === `-${command}`); - if (index >= 0) { - conflicts.push({ command, index }); - } + const local = localByCommand.get(command); + const remote = remoteByCommand.get(command); + mergeContent = this.updateKeybinding(mergeContent, eol, command, [...local || [], ...remote || []]); + conflicts.push({ command, local, remote, firstIndex: -1 }); } - conflicts.sort((a, b) => b.index - a.index); - // Move all entries with same command together + const allKeybindings = parse(mergeContent); + const tree = parseTree(mergeContent); for (const conflict of conflicts) { - this.updateKeybinding(keybindingsPreviewModel, conflict.command, localByCommand.get(conflict.command)!); + conflict.firstIndex = findFirstIndex(allKeybindings, keybinding => keybinding.command === conflict.command || keybinding.command === `-${conflict.command}`); } + // Sort reverse so that conflicts content is added from last + conflicts.sort((a, b) => b.firstIndex - a.firstIndex); - const eol = keybindingsPreviewModel.getEOL(); - for (const conflict of conflicts) { - const tree = parseTree(keybindingsPreviewModel.getValue()); - const valueNode = findNodeAtLocation(tree, [conflict.index]); - const remoteContent = this.getRemoteContentForConflict(eol, remoteByCommand.get(conflict.command)!); - if (valueNode) { - // Updated in Local and Remote with different value - const valueStartPosition = keybindingsPreviewModel.getPositionAt(valueNode.offset); - const valueEndPosition = keybindingsPreviewModel.getPositionAt(valueNode.offset + valueNode.length); - const editOperations = [ - EditOperation.insert(new Position(valueStartPosition.lineNumber - 1, keybindingsPreviewModel.getLineMaxColumn(valueStartPosition.lineNumber - 1)), `${eol}<<<<<<< local`), - EditOperation.insert(new Position(valueEndPosition.lineNumber, keybindingsPreviewModel.getLineMaxColumn(valueEndPosition.lineNumber)), `${eol}=======${eol}${remoteContent}>>>>>>> remote`) - ]; - keybindingsPreviewModel.pushEditOperations([new Selection(valueStartPosition.lineNumber, valueStartPosition.column, valueStartPosition.lineNumber, valueStartPosition.column)], editOperations, () => []); - } else { - // Removed in Local, but updated in Remote - const position = new Position(keybindingsPreviewModel.getLineCount() - 1, keybindingsPreviewModel.getLineMaxColumn(keybindingsPreviewModel.getLineCount() - 1)); - const editOperations = [ - EditOperation.insert(position, `${eol}<<<<<<< local${eol}=======${eol}${remoteContent}>>>>>>> remote`) - ]; - keybindingsPreviewModel.pushEditOperations([new Selection(position.lineNumber, position.column, position.lineNumber, position.column)], editOperations, () => []); + const model = this.modelService.createModel(mergeContent, null); + for (const { firstIndex, local, remote } of conflicts) { + const firstNode = findNodeAtLocation(tree, [firstIndex])!; + const fistNodePosition = model.getPositionAt(firstNode.offset); + const startLocalOffset = model.getOffsetAt(new Position(fistNodePosition.lineNumber - 1, model.getLineMaxColumn(fistNodePosition.lineNumber - 1))); + let endLocalOffset = startLocalOffset; + let remoteOffset = endLocalOffset; + if (local) { + const lastLocalValueNode = findNodeAtLocation(tree, [firstIndex + local.length - 1])!; + const lastLocalValueEndPosition = model.getPositionAt(lastLocalValueNode.offset + lastLocalValueNode.length); + endLocalOffset = model.getOffsetAt(new Position(lastLocalValueEndPosition.lineNumber, model.getLineMaxColumn(lastLocalValueEndPosition.lineNumber))); } + if (remote) { + const lastRemoteValueNode = findNodeAtLocation(tree, [firstIndex + (local ? local.length : 0) + remote.length - 1])!; + const lastRemoteValueEndPosition = model.getPositionAt(lastRemoteValueNode.offset + lastRemoteValueNode.length); + remoteOffset = model.getOffsetAt(new Position(lastRemoteValueEndPosition.lineNumber, model.getLineMaxColumn(lastRemoteValueEndPosition.lineNumber))); + } + mergeContent = mergeContent.substring(0, startLocalOffset) + + `${eol}<<<<<<< local` + + mergeContent.substring(startLocalOffset, endLocalOffset) + + `${eol}=======` + + mergeContent.substring(endLocalOffset, remoteOffset) + + `${eol}>>>>>>> remote` + + mergeContent.substring(remoteOffset); } - return { mergeContent: keybindingsPreviewModel.getValue(), hasChanges: true, hasConflicts: conflicts.length > 0 }; - } - - private getRemoteContentForConflict(eol: string, keybindings: IUserFriendlyKeybinding[]): string { - let content = `[${eol}${eol}]`; - for (const keybinding of keybindings) { - const edit = setProperty(content, [-1], keybinding, { tabSize: 4, insertSpaces: false, eol })[0]; - content = content.substring(0, edit.offset) + edit.content + content.substring(edit.offset + edit.length); - } - return content.substring(2, content.length - 2); + return { mergeContent, hasChanges: true, hasConflicts: conflicts.length > 0 }; } private compare(from: Map, to: Map): { added: Set, removed: Set, updated: Set } { @@ -251,48 +241,45 @@ export class KeybindingsMergeService implements IKeybindingsMergeService { return true; } - private addKeybinding(model: ITextModel, command: string, keybindings: IUserFriendlyKeybinding[]): void { + private addKeybinding(content: string, eol: string, command: string, keybindings: IUserFriendlyKeybinding[]): string { for (const keybinding of keybindings) { - this.edit(model, [-1], keybinding); + content = this.edit(content, eol, [-1], keybinding); } + return content; } - private removeKeybindings(model: ITextModel, command: string): void { - const keybindings = parse(model.getValue()); + private removeKeybindings(content: string, eol: string, command: string): string { + const keybindings = parse(content); for (let index = keybindings.length - 1; index >= 0; index--) { if (keybindings[index].command === command || keybindings[index].command === `-${command}`) { - this.edit(model, [index], undefined); + content = this.edit(content, eol, [index], undefined); } } + return content; } - private updateKeybinding(model: ITextModel, command: string, keybindings: IUserFriendlyKeybinding[]): void { - const allKeybindings = parse(model.getValue()); - const location = firstIndex(allKeybindings, keybinding => keybinding.command === command || keybinding.command === `-${command}`); + private updateKeybinding(content: string, eol: string, command: string, keybindings: IUserFriendlyKeybinding[]): string { + const allKeybindings = parse(content); + const location = findFirstIndex(allKeybindings, keybinding => keybinding.command === command || keybinding.command === `-${command}`); // Remove all entries with this command for (let index = allKeybindings.length - 1; index >= 0; index--) { if (allKeybindings[index].command === command || allKeybindings[index].command === `-${command}`) { - this.edit(model, [index], undefined); + content = this.edit(content, eol, [index], undefined); } } // add all entries at the same location where the entry with this command was located. for (let index = keybindings.length - 1; index >= 0; index--) { - this.edit(model, [location], keybindings[index]); + content = this.edit(content, eol, [location], keybindings[index]); } + return content; } - private edit(model: ITextModel, originalPath: JSONPath, value: any) { - const edit = setProperty(model.getValue(), originalPath, value, { tabSize: 4, insertSpaces: false, eol: model.getEOL() })[0]; + private edit(content: string, eol: string, originalPath: JSONPath, value: any): string { + const edit = setProperty(content, originalPath, value, { tabSize: 4, insertSpaces: false, eol })[0]; if (edit) { - const startPosition = model.getPositionAt(edit.offset); - const endPosition = model.getPositionAt(edit.offset + edit.length); - const range = new Range(startPosition.lineNumber, startPosition.column, endPosition.lineNumber, endPosition.column); - let currentText = model.getValueInRange(range); - if (edit.content !== currentText) { - const editOperation = currentText ? EditOperation.replace(range, edit.content) : EditOperation.insert(startPosition, edit.content); - model.pushEditOperations([new Selection(startPosition.lineNumber, startPosition.column, startPosition.lineNumber, startPosition.column)], [editOperation], () => []); - } + content = content.substring(0, edit.offset) + edit.content + content.substring(edit.offset + edit.length); } + return content; } } diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingsMerge.test.ts b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingsMerge.test.ts index 0399e01b4f2..495bc762c52 100644 --- a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingsMerge.test.ts +++ b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingsMerge.test.ts @@ -402,7 +402,7 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+c', command: '-a' }, { key: 'cmd+e', command: 'd' }, - { key: 'alt+f', command: 'f' }, + { key: 'alt+a', command: 'f' }, { key: 'alt+d', command: '-f' }, { key: 'cmd+d', command: 'c', when: 'context1' }, { key: 'cmd+c', command: '-c' }, @@ -416,6 +416,7 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+e', command: 'e' }, ]); const remoteContent = stringify([ + { key: 'alt+a', command: 'f' }, { key: 'cmd+c', command: '-c' }, { key: 'cmd+d', command: 'd' }, { key: 'alt+d', command: '-f' }, @@ -454,7 +455,131 @@ suite('KeybindingsMerge - Conflicts', () => { "key": "alt+d", "command": "a", "when": "editorTextFocus && !editorReadonly" + }, +======= + { + "key": "alt+c", + "command": "a", + "when": "editorTextFocus && !editorReadonly" } +>>>>>>> remote +]`); + }); + + test('merge when local and remote with different keybinding', async () => { + const localContent = stringify([ + { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+a', command: '-a', when: 'editorTextFocus && !editorReadonly' } + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+a', command: '-a', when: 'editorTextFocus && !editorReadonly' } + ]); + const actual = await testObject.merge(localContent, remoteContent, null); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `[ +<<<<<<< local + { + "key": "alt+d", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + }, + { + "key": "alt+a", + "command": "-a", + "when": "editorTextFocus && !editorReadonly" + }, +======= + { + "key": "alt+c", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + }, + { + "key": "alt+a", + "command": "-a", + "when": "editorTextFocus && !editorReadonly" + } +>>>>>>> remote +]`); + }); + + test('merge when local and remote has entries in different order', async () => { + const localContent = stringify([ + { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+a', command: 'a', when: 'editorTextFocus' } + ]); + const remoteContent = stringify([ + { key: 'alt+a', command: 'a', when: 'editorTextFocus' }, + { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' } + ]); + const actual = await testObject.merge(localContent, remoteContent, null); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `[ +<<<<<<< local + { + "key": "alt+d", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + }, + { + "key": "alt+a", + "command": "a", + "when": "editorTextFocus" + }, +======= + { + "key": "alt+a", + "command": "a", + "when": "editorTextFocus" + }, + { + "key": "alt+d", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + } +>>>>>>> remote +]`); + }); + + test('merge when the entry is removed in local but updated in remote', async () => { + const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const localContent = stringify([]); + const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const actual = await testObject.merge(localContent, remoteContent, baseContent); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `[ +<<<<<<< local +======= + { + "key": "alt+c", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + } +>>>>>>> remote +]`); + }); + + test('merge when the entry is removed in local but updated in remote and a new entry is added in local', async () => { + const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const localContent = stringify([{ key: 'alt+b', command: 'b' }]); + const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const actual = await testObject.merge(localContent, remoteContent, baseContent); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `[ + { + "key": "alt+b", + "command": "b" + }, +<<<<<<< local ======= { "key": "alt+c", From 394645269aad419a45855737f6b58e35f03ab43c Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 25 Nov 2019 00:30:37 +0100 Subject: [PATCH 031/255] :lipstick: --- src/vs/workbench/services/keybinding/common/keybindingsMerge.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/services/keybinding/common/keybindingsMerge.ts b/src/vs/workbench/services/keybinding/common/keybindingsMerge.ts index 006fd5005dd..6b33cccbb8f 100644 --- a/src/vs/workbench/services/keybinding/common/keybindingsMerge.ts +++ b/src/vs/workbench/services/keybinding/common/keybindingsMerge.ts @@ -151,7 +151,6 @@ export class KeybindingsMergeService implements IKeybindingsMergeService { } const allKeybindings = parse(mergeContent); - const tree = parseTree(mergeContent); for (const conflict of conflicts) { conflict.firstIndex = findFirstIndex(allKeybindings, keybinding => keybinding.command === conflict.command || keybinding.command === `-${conflict.command}`); } @@ -159,6 +158,7 @@ export class KeybindingsMergeService implements IKeybindingsMergeService { conflicts.sort((a, b) => b.firstIndex - a.firstIndex); const model = this.modelService.createModel(mergeContent, null); + const tree = parseTree(mergeContent); for (const { firstIndex, local, remote } of conflicts) { const firstNode = findNodeAtLocation(tree, [firstIndex])!; const fistNodePosition = model.getPositionAt(firstNode.offset); From 7be711033186594e7bef3b0c4161f8f9bf945b88 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 25 Nov 2019 11:05:33 +0100 Subject: [PATCH 032/255] remove text model and move to platform --- .../platform/userDataSync/common/content.ts | 64 ++++ .../userDataSync/common/keybindingsMerge.ts | 259 ++++++++++++++++ .../test}/keybindingsMerge.test.ts | 121 ++++---- .../keybinding/common/keybindingsMerge.ts | 287 ------------------ 4 files changed, 382 insertions(+), 349 deletions(-) create mode 100644 src/vs/platform/userDataSync/common/content.ts create mode 100644 src/vs/platform/userDataSync/common/keybindingsMerge.ts rename src/vs/{workbench/services/keybinding/test/electron-browser => platform/userDataSync/test}/keybindingsMerge.test.ts (83%) delete mode 100644 src/vs/workbench/services/keybinding/common/keybindingsMerge.ts diff --git a/src/vs/platform/userDataSync/common/content.ts b/src/vs/platform/userDataSync/common/content.ts new file mode 100644 index 00000000000..f50d0c0bfb1 --- /dev/null +++ b/src/vs/platform/userDataSync/common/content.ts @@ -0,0 +1,64 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { OperatingSystem, OS } from 'vs/base/common/platform'; +import { JSONPath } from 'vs/base/common/json'; +import { setProperty } from 'vs/base/common/jsonEdit'; + + +export function edit(content: string, eol: string, originalPath: JSONPath, value: any): string { + const edit = setProperty(content, originalPath, value, { tabSize: 4, insertSpaces: false, eol })[0]; + if (edit) { + content = content.substring(0, edit.offset) + edit.content + content.substring(edit.offset + edit.length); + } + return content; +} + +export function getLineStartOffset(content: string, eol: string, atOffset: number): number { + let lineStartingOffset = atOffset; + while (lineStartingOffset >= 0) { + if (content.charAt(lineStartingOffset) === eol.charAt(eol.length - 1)) { + if (eol.length === 1) { + return lineStartingOffset + 1; + } + } + lineStartingOffset--; + if (eol.length === 2) { + if (lineStartingOffset >= 0 && content.charAt(lineStartingOffset) === eol.charAt(0)) { + return lineStartingOffset + 2; + } + } + } + return 0; +} + +export function getLineEndOffset(content: string, eol: string, atOffset: number): number { + let lineEndOffset = atOffset; + while (lineEndOffset >= 0) { + if (content.charAt(lineEndOffset) === eol.charAt(eol.length - 1)) { + if (eol.length === 1) { + return lineEndOffset; + } + } + lineEndOffset++; + if (eol.length === 2) { + if (lineEndOffset >= 0 && content.charAt(lineEndOffset) === eol.charAt(1)) { + return lineEndOffset; + } + } + } + return content.length - 1; +} + +export function getEol(content: string): string { + if (content.indexOf('\r\n') !== -1) { + return '\r\n'; + } + if (content.indexOf('\n') !== -1) { + return '\n'; + } + return OS === OperatingSystem.Linux || OS === OperatingSystem.Macintosh ? '\n' : '\r\n'; +} + diff --git a/src/vs/platform/userDataSync/common/keybindingsMerge.ts b/src/vs/platform/userDataSync/common/keybindingsMerge.ts new file mode 100644 index 00000000000..731a1309b39 --- /dev/null +++ b/src/vs/platform/userDataSync/common/keybindingsMerge.ts @@ -0,0 +1,259 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as objects from 'vs/base/common/objects'; +import { parse, findNodeAtLocation, parseTree } from 'vs/base/common/json'; +import { values, keys } from 'vs/base/common/map'; +import { IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding'; +import { firstIndex as findFirstIndex, equals } from 'vs/base/common/arrays'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import * as contentUtil from 'vs/platform/userDataSync/common/content'; + +export function mergeKeybindings(localContent: string, remoteContent: string, baseContent: string | null): { mergeContent: string, hasChanges: boolean, hasConflicts: boolean } { + const local = parse(localContent); + const remote = parse(remoteContent); + const base = baseContent ? parse(baseContent) : null; + + const byCommand = (keybindings: IUserFriendlyKeybinding[]) => { + const map: Map = new Map(); + for (const keybinding of keybindings) { + const command = keybinding.command[0] === '-' ? keybinding.command.substring(1) : keybinding.command; + let value = map.get(command); + if (!value) { + value = []; + map.set(command, value); + } + value.push(keybinding); + } + return map; + }; + + const localByCommand = byCommand(local); + const remoteByCommand = byCommand(remote); + const baseByCommand = base ? byCommand(base) : null; + + const localToRemote = compare(localByCommand, remoteByCommand); + if (localToRemote.added.size === 0 && localToRemote.removed.size === 0 && localToRemote.updated.size === 0) { + // No changes found between local and remote. + return { mergeContent: localContent, hasChanges: false, hasConflicts: false }; + } + + const conflictCommands: Set = new Set(); + const baseToLocal = baseByCommand ? compare(baseByCommand, localByCommand) : { added: keys(localByCommand).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; + const baseToRemote = baseByCommand ? compare(baseByCommand, remoteByCommand) : { added: keys(remoteByCommand).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; + const eol = contentUtil.getEol(localContent); + let mergeContent = localContent; + + // Removed commands in Local + for (const command of values(baseToLocal.removed)) { + // Got updated in remote + if (baseToRemote.updated.has(command)) { + conflictCommands.add(command); + } + } + + // Removed commands in Remote + for (const command of values(baseToRemote.removed)) { + if (conflictCommands.has(command)) { + continue; + } + // Got updated in local + if (baseToLocal.updated.has(command)) { + conflictCommands.add(command); + } else { + // remove the command + mergeContent = removeKeybindings(mergeContent, eol, command); + } + } + + // Added commands in Local + for (const command of values(baseToLocal.added)) { + if (conflictCommands.has(command)) { + continue; + } + // Got added in remote + if (baseToRemote.added.has(command)) { + // Has different value + if (localToRemote.updated.has(command)) { + conflictCommands.add(command); + } + } + } + + // Added commands in remote + for (const command of values(baseToRemote.added)) { + if (conflictCommands.has(command)) { + continue; + } + // Got added in local + if (baseToLocal.added.has(command)) { + // Has different value + if (localToRemote.updated.has(command)) { + conflictCommands.add(command); + } + } else { + mergeContent = addKeybinding(mergeContent, eol, command, remoteByCommand.get(command)!); + } + } + + // Updated commands in Local + for (const command of values(baseToLocal.updated)) { + if (conflictCommands.has(command)) { + continue; + } + // Got updated in remote + if (baseToRemote.updated.has(command)) { + // Has different value + if (localToRemote.updated.has(command)) { + conflictCommands.add(command); + } + } + } + + // Updated commands in Remote + for (const command of values(baseToRemote.updated)) { + if (conflictCommands.has(command)) { + continue; + } + // Got updated in local + if (baseToLocal.updated.has(command)) { + // Has different value + if (localToRemote.updated.has(command)) { + conflictCommands.add(command); + } + } else { + // update the command + mergeContent = updateKeybinding(mergeContent, eol, command, remoteByCommand.get(command)!); + } + } + + const conflicts: { command: string, local: IUserFriendlyKeybinding[] | undefined, remote: IUserFriendlyKeybinding[] | undefined, firstIndex: number }[] = []; + + for (const command of values(conflictCommands)) { + const local = localByCommand.get(command); + const remote = remoteByCommand.get(command); + mergeContent = updateKeybinding(mergeContent, eol, command, [...local || [], ...remote || []]); + conflicts.push({ command, local, remote, firstIndex: -1 }); + } + + const allKeybindings = parse(mergeContent); + for (const conflict of conflicts) { + conflict.firstIndex = findFirstIndex(allKeybindings, keybinding => keybinding.command === conflict.command || keybinding.command === `-${conflict.command}`); + } + // Sort reverse so that conflicts content is added from last + conflicts.sort((a, b) => b.firstIndex - a.firstIndex); + + const tree = parseTree(mergeContent); + for (const { firstIndex, local, remote } of conflicts) { + const firstNode = findNodeAtLocation(tree, [firstIndex])!; + const startLocalOffset = contentUtil.getLineStartOffset(mergeContent, eol, firstNode.offset) - eol.length; + let endLocalOffset = startLocalOffset; + let remoteOffset = endLocalOffset; + if (local) { + const lastLocalValueNode = findNodeAtLocation(tree, [firstIndex + local.length - 1])!; + endLocalOffset = contentUtil.getLineEndOffset(mergeContent, eol, lastLocalValueNode.offset + lastLocalValueNode.length); + } + if (remote) { + const lastRemoteValueNode = findNodeAtLocation(tree, [firstIndex + (local ? local.length : 0) + remote.length - 1])!; + remoteOffset = contentUtil.getLineEndOffset(mergeContent, eol, lastRemoteValueNode.offset + lastRemoteValueNode.length); + } + mergeContent = mergeContent.substring(0, startLocalOffset) + + `${eol}<<<<<<< local` + + mergeContent.substring(startLocalOffset, endLocalOffset) + + `${eol}=======` + + mergeContent.substring(endLocalOffset, remoteOffset) + + `${eol}>>>>>>> remote` + + mergeContent.substring(remoteOffset); + } + + return { mergeContent, hasChanges: true, hasConflicts: conflicts.length > 0 }; +} + +function compare(from: Map, to: Map): { added: Set, removed: Set, updated: Set } { + const fromKeys = keys(from); + const toKeys = keys(to); + const added = toKeys.filter(key => fromKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); + const removed = fromKeys.filter(key => toKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); + const updated: Set = new Set(); + + for (const key of fromKeys) { + if (removed.has(key)) { + continue; + } + const value1: IUserFriendlyKeybinding[] = from.get(key)!; + const value2: IUserFriendlyKeybinding[] = to.get(key)!; + if (!areSameKeybindings(value1, value2)) { + updated.add(key); + } + } + + return { added, removed, updated }; +} + +function areSameKeybindings(value1: IUserFriendlyKeybinding[], value2: IUserFriendlyKeybinding[]): boolean { + // Compare entries adding keybindings + if (!equals(value1.filter(({ command }) => command[0] !== '-'), value2.filter(({ command }) => command[0] !== '-'), (a, b) => isSameKeybinding(a, b))) { + return false; + } + // Compare entries removing keybindings + if (!equals(value1.filter(({ command }) => command[0] === '-'), value2.filter(({ command }) => command[0] === '-'), (a, b) => isSameKeybinding(a, b))) { + return false; + } + return true; +} + +function isSameKeybinding(a: IUserFriendlyKeybinding, b: IUserFriendlyKeybinding): boolean { + if (a.command !== b.command) { + return false; + } + if (a.key !== b.key) { + return false; + } + const whenA = ContextKeyExpr.deserialize(a.when); + const whenB = ContextKeyExpr.deserialize(b.when); + if ((whenA && !whenB) || (!whenA && whenB)) { + return false; + } + if (whenA && whenB && !whenA.equals(whenB)) { + return false; + } + if (!objects.equals(a.args, b.args)) { + return false; + } + return true; +} + +function addKeybinding(content: string, eol: string, command: string, keybindings: IUserFriendlyKeybinding[]): string { + for (const keybinding of keybindings) { + content = contentUtil.edit(content, eol, [-1], keybinding); + } + return content; +} + +function removeKeybindings(content: string, eol: string, command: string): string { + const keybindings = parse(content); + for (let index = keybindings.length - 1; index >= 0; index--) { + if (keybindings[index].command === command || keybindings[index].command === `-${command}`) { + content = contentUtil.edit(content, eol, [index], undefined); + } + } + return content; +} + +function updateKeybinding(content: string, eol: string, command: string, keybindings: IUserFriendlyKeybinding[]): string { + const allKeybindings = parse(content); + const location = findFirstIndex(allKeybindings, keybinding => keybinding.command === command || keybinding.command === `-${command}`); + // Remove all entries with this command + for (let index = allKeybindings.length - 1; index >= 0; index--) { + if (allKeybindings[index].command === command || allKeybindings[index].command === `-${command}`) { + content = contentUtil.edit(content, eol, [index], undefined); + } + } + // add all entries at the same location where the entry with this command was located. + for (let index = keybindings.length - 1; index >= 0; index--) { + content = contentUtil.edit(content, eol, [location], keybindings[index]); + } + return content; +} diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingsMerge.test.ts b/src/vs/platform/userDataSync/test/keybindingsMerge.test.ts similarity index 83% rename from src/vs/workbench/services/keybinding/test/electron-browser/keybindingsMerge.test.ts rename to src/vs/platform/userDataSync/test/keybindingsMerge.test.ts index 495bc762c52..25a2fb0d116 100644 --- a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingsMerge.test.ts +++ b/src/vs/platform/userDataSync/test/keybindingsMerge.test.ts @@ -4,34 +4,29 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { workbenchInstantiationService } from 'vs/workbench/test/workbenchTestServices'; -import { KeybindingsMergeService } from 'vs/workbench/services/keybinding/common/keybindingsMerge'; - -let testObject: KeybindingsMergeService; - -suiteSetup(() => testObject = workbenchInstantiationService().createInstance(KeybindingsMergeService)); +import { mergeKeybindings } from 'vs/platform/userDataSync/common/keybindingsMerge'; suite('KeybindingsMerge - No Conflicts', () => { - test('merge when local and remote are same with one entry', async () => { + test('merge when local and remote are same with one entry', () => { const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const actual = await testObject.merge(localContent, remoteContent, null); + const actual = mergeKeybindings(localContent, remoteContent, null); assert.ok(!actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); }); - test('merge when local and remote are same with similar when contexts', async () => { + test('merge when local and remote are same with similar when contexts', () => { const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: '!editorReadonly && editorTextFocus' }]); - const actual = await testObject.merge(localContent, remoteContent, null); + const actual = mergeKeybindings(localContent, remoteContent, null); assert.ok(!actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); }); - test('merge when local and remote are same with multiple entries', async () => { + test('merge when local and remote are same with multiple entries', () => { const localContent = stringify([ { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, @@ -42,13 +37,13 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+d', command: '-a' }, { key: 'cmd+c', command: 'b', args: { text: '`' } } ]); - const actual = await testObject.merge(localContent, remoteContent, null); + const actual = mergeKeybindings(localContent, remoteContent, null); assert.ok(!actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); }); - test('merge when local and remote are same with different base content', async () => { + test('merge when local and remote are same with different base content', () => { const localContent = stringify([ { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, @@ -63,13 +58,13 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+d', command: '-a' }, { key: 'cmd+c', command: 'b', args: { text: '`' } } ]); - const actual = await testObject.merge(localContent, remoteContent, baseContent); + const actual = mergeKeybindings(localContent, remoteContent, baseContent); assert.ok(!actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); }); - test('merge when local and remote are same with multiple entries in different order', async () => { + test('merge when local and remote are same with multiple entries in different order', () => { const localContent = stringify([ { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, @@ -80,13 +75,13 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, ]); - const actual = await testObject.merge(localContent, remoteContent, null); + const actual = mergeKeybindings(localContent, remoteContent, null); assert.ok(!actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); }); - test('merge when local and remote are same when remove entry is in different order', async () => { + test('merge when local and remote are same when remove entry is in different order', () => { const localContent = stringify([ { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, @@ -97,13 +92,13 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'cmd+c', command: 'b', args: { text: '`' } }, ]); - const actual = await testObject.merge(localContent, remoteContent, null); + const actual = mergeKeybindings(localContent, remoteContent, null); assert.ok(!actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); }); - test('merge when a new entry is added to remote', async () => { + test('merge when a new entry is added to remote', () => { const localContent = stringify([ { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, @@ -113,13 +108,13 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+d', command: '-a' }, { key: 'cmd+c', command: 'b', args: { text: '`' } }, ]); - const actual = await testObject.merge(localContent, remoteContent, null); + const actual = mergeKeybindings(localContent, remoteContent, null); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, remoteContent); }); - test('merge when multiple new entries are added to remote', async () => { + test('merge when multiple new entries are added to remote', () => { const localContent = stringify([ { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, @@ -130,13 +125,13 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'cmd+c', command: 'b', args: { text: '`' } }, { key: 'cmd+d', command: 'c' }, ]); - const actual = await testObject.merge(localContent, remoteContent, null); + const actual = mergeKeybindings(localContent, remoteContent, null); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, remoteContent); }); - test('merge when multiple new entries are added to remote from base and local has not changed', async () => { + test('merge when multiple new entries are added to remote from base and local has not changed', () => { const localContent = stringify([ { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, @@ -147,13 +142,13 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'cmd+c', command: 'b', args: { text: '`' } }, { key: 'cmd+d', command: 'c' }, ]); - const actual = await testObject.merge(localContent, remoteContent, localContent); + const actual = mergeKeybindings(localContent, remoteContent, localContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, remoteContent); }); - test('merge when an entry is removed from remote from base and local has not changed', async () => { + test('merge when an entry is removed from remote from base and local has not changed', () => { const localContent = stringify([ { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, @@ -163,13 +158,13 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, ]); - const actual = await testObject.merge(localContent, remoteContent, localContent); + const actual = mergeKeybindings(localContent, remoteContent, localContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, remoteContent); }); - test('merge when an entry (same command) is removed from remote from base and local has not changed', async () => { + test('merge when an entry (same command) is removed from remote from base and local has not changed', () => { const localContent = stringify([ { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, @@ -177,26 +172,26 @@ suite('KeybindingsMerge - No Conflicts', () => { const remoteContent = stringify([ { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, ]); - const actual = await testObject.merge(localContent, remoteContent, localContent); + const actual = mergeKeybindings(localContent, remoteContent, localContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, remoteContent); }); - test('merge when an entry is updated in remote from base and local has not changed', async () => { + test('merge when an entry is updated in remote from base and local has not changed', () => { const localContent = stringify([ { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, ]); const remoteContent = stringify([ { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, ]); - const actual = await testObject.merge(localContent, remoteContent, localContent); + const actual = mergeKeybindings(localContent, remoteContent, localContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, remoteContent); }); - test('merge when a command with multiple entries is updated from remote from base and local has not changed', async () => { + test('merge when a command with multiple entries is updated from remote from base and local has not changed', () => { const localContent = stringify([ { key: 'shift+c', command: 'c' }, { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, @@ -215,13 +210,13 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'cmd+d', command: 'a' }, { key: 'alt+d', command: 'b' }, ]); - const actual = await testObject.merge(localContent, remoteContent, localContent); + const actual = mergeKeybindings(localContent, remoteContent, localContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, expected); }); - test('merge when remote has moved forwareded with multiple changes and local stays with base', async () => { + test('merge when remote has moved forwareded with multiple changes and local stays with base', () => { const localContent = stringify([ { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'cmd+c', command: 'b', args: { text: '`' } }, @@ -247,13 +242,13 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+f', command: 'f' }, { key: 'alt+d', command: '-f' }, ]); - const actual = await testObject.merge(localContent, remoteContent, localContent); + const actual = mergeKeybindings(localContent, remoteContent, localContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, expected); }); - test('merge when a new entry is added to local', async () => { + test('merge when a new entry is added to local', () => { const localContent = stringify([ { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, @@ -263,13 +258,13 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, ]); - const actual = await testObject.merge(localContent, remoteContent, null); + const actual = mergeKeybindings(localContent, remoteContent, null); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); }); - test('merge when multiple new entries are added to local', async () => { + test('merge when multiple new entries are added to local', () => { const localContent = stringify([ { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, @@ -280,13 +275,13 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, ]); - const actual = await testObject.merge(localContent, remoteContent, null); + const actual = mergeKeybindings(localContent, remoteContent, null); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); }); - test('merge when multiple new entries are added to local from base and remote is not changed', async () => { + test('merge when multiple new entries are added to local from base and remote is not changed', () => { const localContent = stringify([ { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, @@ -297,13 +292,13 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, ]); - const actual = await testObject.merge(localContent, remoteContent, remoteContent); + const actual = mergeKeybindings(localContent, remoteContent, remoteContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); }); - test('merge when an entry is removed from local from base and remote has not changed', async () => { + test('merge when an entry is removed from local from base and remote has not changed', () => { const localContent = stringify([ { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, @@ -313,13 +308,13 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+d', command: '-a' }, { key: 'cmd+c', command: 'b', args: { text: '`' } }, ]); - const actual = await testObject.merge(localContent, remoteContent, remoteContent); + const actual = mergeKeybindings(localContent, remoteContent, remoteContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); }); - test('merge when an entry (with same command) is removed from local from base and remote has not changed', async () => { + test('merge when an entry (with same command) is removed from local from base and remote has not changed', () => { const localContent = stringify([ { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, ]); @@ -327,26 +322,26 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, ]); - const actual = await testObject.merge(localContent, remoteContent, remoteContent); + const actual = mergeKeybindings(localContent, remoteContent, remoteContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); }); - test('merge when an entry is updated in local from base and remote has not changed', async () => { + test('merge when an entry is updated in local from base and remote has not changed', () => { const localContent = stringify([ { key: 'alt+d', command: 'a', when: 'editorTextFocus' }, ]); const remoteContent = stringify([ { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, ]); - const actual = await testObject.merge(localContent, remoteContent, remoteContent); + const actual = mergeKeybindings(localContent, remoteContent, remoteContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); }); - test('merge when a command with multiple entries is updated from local from base and remote has not changed', async () => { + test('merge when a command with multiple entries is updated from local from base and remote has not changed', () => { const localContent = stringify([ { key: 'shift+c', command: 'c' }, { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, @@ -359,13 +354,13 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+d', command: 'b' }, { key: 'cmd+d', command: 'a' }, ]); - const actual = await testObject.merge(localContent, remoteContent, remoteContent); + const actual = mergeKeybindings(localContent, remoteContent, remoteContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); }); - test('merge when local has moved forwareded with multiple changes and remote stays with base', async () => { + test('merge when local has moved forwareded with multiple changes and remote stays with base', () => { const localContent = stringify([ { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'cmd+e', command: 'd' }, @@ -391,13 +386,13 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'cmd+d', command: 'c', when: 'context1' }, { key: 'cmd+c', command: '-c' }, ]); - const actual = await testObject.merge(localContent, remoteContent, remoteContent); + const actual = mergeKeybindings(localContent, remoteContent, remoteContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, expected); }); - test('merge when local and remote has moved forwareded with no conflicts', async () => { + test('merge when local and remote has moved forwareded with no conflicts', () => { const baseContent = stringify([ { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+c', command: '-a' }, @@ -432,7 +427,7 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+e', command: 'e' }, { key: 'alt+g', command: 'g', when: 'context2' }, ]); - const actual = await testObject.merge(localContent, remoteContent, baseContent); + const actual = mergeKeybindings(localContent, remoteContent, baseContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, expected); @@ -442,10 +437,10 @@ suite('KeybindingsMerge - No Conflicts', () => { suite('KeybindingsMerge - Conflicts', () => { - test('merge when local and remote with one entry but different value', async () => { + test('merge when local and remote with one entry but different value', () => { const localContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const actual = await testObject.merge(localContent, remoteContent, null); + const actual = mergeKeybindings(localContent, remoteContent, null); assert.ok(actual.hasChanges); assert.ok(actual.hasConflicts); assert.equal(actual.mergeContent, @@ -466,7 +461,7 @@ suite('KeybindingsMerge - Conflicts', () => { ]`); }); - test('merge when local and remote with different keybinding', async () => { + test('merge when local and remote with different keybinding', () => { const localContent = stringify([ { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+a', command: '-a', when: 'editorTextFocus && !editorReadonly' } @@ -475,7 +470,7 @@ suite('KeybindingsMerge - Conflicts', () => { { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+a', command: '-a', when: 'editorTextFocus && !editorReadonly' } ]); - const actual = await testObject.merge(localContent, remoteContent, null); + const actual = mergeKeybindings(localContent, remoteContent, null); assert.ok(actual.hasChanges); assert.ok(actual.hasConflicts); assert.equal(actual.mergeContent, @@ -506,7 +501,7 @@ suite('KeybindingsMerge - Conflicts', () => { ]`); }); - test('merge when local and remote has entries in different order', async () => { + test('merge when local and remote has entries in different order', () => { const localContent = stringify([ { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+a', command: 'a', when: 'editorTextFocus' } @@ -515,7 +510,7 @@ suite('KeybindingsMerge - Conflicts', () => { { key: 'alt+a', command: 'a', when: 'editorTextFocus' }, { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' } ]); - const actual = await testObject.merge(localContent, remoteContent, null); + const actual = mergeKeybindings(localContent, remoteContent, null); assert.ok(actual.hasChanges); assert.ok(actual.hasConflicts); assert.equal(actual.mergeContent, @@ -546,11 +541,13 @@ suite('KeybindingsMerge - Conflicts', () => { ]`); }); - test('merge when the entry is removed in local but updated in remote', async () => { + test('merge when the entry is removed in local but updated in remote', () => { const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); const localContent = stringify([]); const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const actual = await testObject.merge(localContent, remoteContent, baseContent); + const actual = mergeKeybindings(localContent, remoteContent, baseContent); + //'[\n<<<<<<< local\n\n=======\t{\n\t\t"key": "alt+c",\n\t\t"command": "a",\n\t\t"when": "editorTextFocus && !editorReadonly"\n\t}\n>>>>>>> remote\n]' + //'[\n<<<<<<< local\n=======\n\t{\n\t\t"key": "alt+c",\n\t\t"command": "a",\n\t\t"when": "editorTextFocus && !editorReadonly"\n\t}\n>>>>>>> remote\n]' assert.ok(actual.hasChanges); assert.ok(actual.hasConflicts); assert.equal(actual.mergeContent, @@ -566,11 +563,11 @@ suite('KeybindingsMerge - Conflicts', () => { ]`); }); - test('merge when the entry is removed in local but updated in remote and a new entry is added in local', async () => { + test('merge when the entry is removed in local but updated in remote and a new entry is added in local', () => { const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); const localContent = stringify([{ key: 'alt+b', command: 'b' }]); const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const actual = await testObject.merge(localContent, remoteContent, baseContent); + const actual = mergeKeybindings(localContent, remoteContent, baseContent); assert.ok(actual.hasChanges); assert.ok(actual.hasConflicts); assert.equal(actual.mergeContent, diff --git a/src/vs/workbench/services/keybinding/common/keybindingsMerge.ts b/src/vs/workbench/services/keybinding/common/keybindingsMerge.ts deleted file mode 100644 index 6b33cccbb8f..00000000000 --- a/src/vs/workbench/services/keybinding/common/keybindingsMerge.ts +++ /dev/null @@ -1,287 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as objects from 'vs/base/common/objects'; -import { parse, findNodeAtLocation, parseTree, JSONPath } from 'vs/base/common/json'; -import { setProperty } from 'vs/base/common/jsonEdit'; -import { IModelService } from 'vs/editor/common/services/modelService'; -import { Position } from 'vs/editor/common/core/position'; -import { values, keys } from 'vs/base/common/map'; -import { IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding'; -import { firstIndex as findFirstIndex, equals } from 'vs/base/common/arrays'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IKeybindingsMergeService } from 'vs/platform/userDataSync/common/userDataSync'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; - -export class KeybindingsMergeService implements IKeybindingsMergeService { - - _serviceBrand: undefined; - - constructor( - @IModelService private readonly modelService: IModelService - ) { } - - async merge(localContent: string, remoteContent: string, baseContent: string | null): Promise<{ mergeContent: string, hasChanges: boolean, hasConflicts: boolean }> { - const local = parse(localContent); - const remote = parse(remoteContent); - const base = baseContent ? parse(baseContent) : null; - - const byCommand = (keybindings: IUserFriendlyKeybinding[]) => { - const map: Map = new Map(); - for (const keybinding of keybindings) { - const command = keybinding.command[0] === '-' ? keybinding.command.substring(1) : keybinding.command; - let value = map.get(command); - if (!value) { - value = []; - map.set(command, value); - } - value.push(keybinding); - } - return map; - }; - - const localByCommand = byCommand(local); - const remoteByCommand = byCommand(remote); - const baseByCommand = base ? byCommand(base) : null; - - const localToRemote = this.compare(localByCommand, remoteByCommand); - if (localToRemote.added.size === 0 && localToRemote.removed.size === 0 && localToRemote.updated.size === 0) { - // No changes found between local and remote. - return { mergeContent: localContent, hasChanges: false, hasConflicts: false }; - } - - const conflictCommands: Set = new Set(); - const baseToLocal = baseByCommand ? this.compare(baseByCommand, localByCommand) : { added: keys(localByCommand).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; - const baseToRemote = baseByCommand ? this.compare(baseByCommand, remoteByCommand) : { added: keys(remoteByCommand).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; - let mergeContent = localContent; - const eol = this.modelService.createModel(mergeContent, null).getEOL(); - - // Removed commands in Local - for (const command of values(baseToLocal.removed)) { - // Got updated in remote - if (baseToRemote.updated.has(command)) { - conflictCommands.add(command); - } - } - - // Removed commands in Remote - for (const command of values(baseToRemote.removed)) { - if (conflictCommands.has(command)) { - continue; - } - // Got updated in local - if (baseToLocal.updated.has(command)) { - conflictCommands.add(command); - } else { - // remove the command - mergeContent = this.removeKeybindings(mergeContent, eol, command); - } - } - - // Added commands in Local - for (const command of values(baseToLocal.added)) { - if (conflictCommands.has(command)) { - continue; - } - // Got added in remote - if (baseToRemote.added.has(command)) { - // Has different value - if (localToRemote.updated.has(command)) { - conflictCommands.add(command); - } - } - } - - // Added commands in remote - for (const command of values(baseToRemote.added)) { - if (conflictCommands.has(command)) { - continue; - } - // Got added in local - if (baseToLocal.added.has(command)) { - // Has different value - if (localToRemote.updated.has(command)) { - conflictCommands.add(command); - } - } else { - mergeContent = this.addKeybinding(mergeContent, eol, command, remoteByCommand.get(command)!); - } - } - - // Updated commands in Local - for (const command of values(baseToLocal.updated)) { - if (conflictCommands.has(command)) { - continue; - } - // Got updated in remote - if (baseToRemote.updated.has(command)) { - // Has different value - if (localToRemote.updated.has(command)) { - conflictCommands.add(command); - } - } - } - - // Updated commands in Remote - for (const command of values(baseToRemote.updated)) { - if (conflictCommands.has(command)) { - continue; - } - // Got updated in local - if (baseToLocal.updated.has(command)) { - // Has different value - if (localToRemote.updated.has(command)) { - conflictCommands.add(command); - } - } else { - // update the command - mergeContent = this.updateKeybinding(mergeContent, eol, command, remoteByCommand.get(command)!); - } - } - - const conflicts: { command: string, local: IUserFriendlyKeybinding[] | undefined, remote: IUserFriendlyKeybinding[] | undefined, firstIndex: number }[] = []; - - for (const command of values(conflictCommands)) { - const local = localByCommand.get(command); - const remote = remoteByCommand.get(command); - mergeContent = this.updateKeybinding(mergeContent, eol, command, [...local || [], ...remote || []]); - conflicts.push({ command, local, remote, firstIndex: -1 }); - } - - const allKeybindings = parse(mergeContent); - for (const conflict of conflicts) { - conflict.firstIndex = findFirstIndex(allKeybindings, keybinding => keybinding.command === conflict.command || keybinding.command === `-${conflict.command}`); - } - // Sort reverse so that conflicts content is added from last - conflicts.sort((a, b) => b.firstIndex - a.firstIndex); - - const model = this.modelService.createModel(mergeContent, null); - const tree = parseTree(mergeContent); - for (const { firstIndex, local, remote } of conflicts) { - const firstNode = findNodeAtLocation(tree, [firstIndex])!; - const fistNodePosition = model.getPositionAt(firstNode.offset); - const startLocalOffset = model.getOffsetAt(new Position(fistNodePosition.lineNumber - 1, model.getLineMaxColumn(fistNodePosition.lineNumber - 1))); - let endLocalOffset = startLocalOffset; - let remoteOffset = endLocalOffset; - if (local) { - const lastLocalValueNode = findNodeAtLocation(tree, [firstIndex + local.length - 1])!; - const lastLocalValueEndPosition = model.getPositionAt(lastLocalValueNode.offset + lastLocalValueNode.length); - endLocalOffset = model.getOffsetAt(new Position(lastLocalValueEndPosition.lineNumber, model.getLineMaxColumn(lastLocalValueEndPosition.lineNumber))); - } - if (remote) { - const lastRemoteValueNode = findNodeAtLocation(tree, [firstIndex + (local ? local.length : 0) + remote.length - 1])!; - const lastRemoteValueEndPosition = model.getPositionAt(lastRemoteValueNode.offset + lastRemoteValueNode.length); - remoteOffset = model.getOffsetAt(new Position(lastRemoteValueEndPosition.lineNumber, model.getLineMaxColumn(lastRemoteValueEndPosition.lineNumber))); - } - mergeContent = mergeContent.substring(0, startLocalOffset) - + `${eol}<<<<<<< local` - + mergeContent.substring(startLocalOffset, endLocalOffset) - + `${eol}=======` - + mergeContent.substring(endLocalOffset, remoteOffset) - + `${eol}>>>>>>> remote` - + mergeContent.substring(remoteOffset); - } - - return { mergeContent, hasChanges: true, hasConflicts: conflicts.length > 0 }; - } - - private compare(from: Map, to: Map): { added: Set, removed: Set, updated: Set } { - const fromKeys = keys(from); - const toKeys = keys(to); - const added = toKeys.filter(key => fromKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); - const removed = fromKeys.filter(key => toKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); - const updated: Set = new Set(); - - for (const key of fromKeys) { - if (removed.has(key)) { - continue; - } - const value1: IUserFriendlyKeybinding[] = from.get(key)!; - const value2: IUserFriendlyKeybinding[] = to.get(key)!; - if (!this.areSameKeybindings(value1, value2)) { - updated.add(key); - } - } - - return { added, removed, updated }; - } - - private areSameKeybindings(value1: IUserFriendlyKeybinding[], value2: IUserFriendlyKeybinding[]): boolean { - // Compare entries adding keybindings - if (!equals(value1.filter(({ command }) => command[0] !== '-'), value2.filter(({ command }) => command[0] !== '-'), (a, b) => this.isSameKeybinding(a, b))) { - return false; - } - // Compare entries removing keybindings - if (!equals(value1.filter(({ command }) => command[0] === '-'), value2.filter(({ command }) => command[0] === '-'), (a, b) => this.isSameKeybinding(a, b))) { - return false; - } - return true; - } - - private isSameKeybinding(a: IUserFriendlyKeybinding, b: IUserFriendlyKeybinding): boolean { - if (a.command !== b.command) { - return false; - } - if (a.key !== b.key) { - return false; - } - const whenA = ContextKeyExpr.deserialize(a.when); - const whenB = ContextKeyExpr.deserialize(b.when); - if ((whenA && !whenB) || (!whenA && whenB)) { - return false; - } - if (whenA && whenB && !whenA.equals(whenB)) { - return false; - } - if (!objects.equals(a.args, b.args)) { - return false; - } - return true; - } - - private addKeybinding(content: string, eol: string, command: string, keybindings: IUserFriendlyKeybinding[]): string { - for (const keybinding of keybindings) { - content = this.edit(content, eol, [-1], keybinding); - } - return content; - } - - private removeKeybindings(content: string, eol: string, command: string): string { - const keybindings = parse(content); - for (let index = keybindings.length - 1; index >= 0; index--) { - if (keybindings[index].command === command || keybindings[index].command === `-${command}`) { - content = this.edit(content, eol, [index], undefined); - } - } - return content; - } - - private updateKeybinding(content: string, eol: string, command: string, keybindings: IUserFriendlyKeybinding[]): string { - const allKeybindings = parse(content); - const location = findFirstIndex(allKeybindings, keybinding => keybinding.command === command || keybinding.command === `-${command}`); - // Remove all entries with this command - for (let index = allKeybindings.length - 1; index >= 0; index--) { - if (allKeybindings[index].command === command || allKeybindings[index].command === `-${command}`) { - content = this.edit(content, eol, [index], undefined); - } - } - // add all entries at the same location where the entry with this command was located. - for (let index = keybindings.length - 1; index >= 0; index--) { - content = this.edit(content, eol, [location], keybindings[index]); - } - return content; - } - - private edit(content: string, eol: string, originalPath: JSONPath, value: any): string { - const edit = setProperty(content, originalPath, value, { tabSize: 4, insertSpaces: false, eol })[0]; - if (edit) { - content = content.substring(0, edit.offset) + edit.content + content.substring(edit.offset + edit.length); - } - return content; - } - -} - -registerSingleton(IKeybindingsMergeService, KeybindingsMergeService); From 63afb956479066265991111a0251f86936469885 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 25 Nov 2019 12:03:32 +0100 Subject: [PATCH 033/255] Enable keybindings synchronization --- .../environment/common/environment.ts | 1 + .../environment/node/environmentService.ts | 3 + .../userDataSync/common/keybindingsSync.ts | 174 +++++++++++++++++- .../userDataSync/common/settingsSync.ts | 5 +- .../userDataSync/common/userDataSync.ts | 7 + .../common/userDataSyncService.ts | 8 +- .../userDataSync/browser/userDataSync.ts | 33 +++- .../environment/browser/environmentService.ts | 3 + .../common/settingsMergeService.ts | 2 +- 9 files changed, 221 insertions(+), 15 deletions(-) diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index 96031eb4ec7..033cdc575f4 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -127,6 +127,7 @@ export interface IEnvironmentService extends IUserHomeProvider { // sync resources userDataSyncLogResource: URI; settingsSyncPreviewResource: URI; + keybindingsSyncPreviewResource: URI; machineSettingsHome: URI; machineSettingsResource: URI; diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts index cd86c866c06..99cab4bba27 100644 --- a/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts @@ -114,6 +114,9 @@ export class EnvironmentService implements IEnvironmentService { @memoize get settingsSyncPreviewResource(): URI { return resources.joinPath(this.userRoamingDataHome, '.settings.json'); } + @memoize + get keybindingsSyncPreviewResource(): URI { return resources.joinPath(this.userRoamingDataHome, '.keybindings.json'); } + @memoize get userDataSyncLogResource(): URI { return URI.file(path.join(this.logsPath, 'userDataSync.log')); } diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index b35731feb4d..b71b73c844e 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -5,7 +5,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IFileService, FileSystemProviderErrorCode, FileSystemProviderError, IFileContent } from 'vs/platform/files/common/files'; -import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, ISynchroniser, SyncStatus, ISettingsMergeService, IUserDataSyncStoreService, DEFAULT_IGNORED_SETTINGS, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, ISynchroniser, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync'; import { VSBuffer } from 'vs/base/common/buffer'; import { parse, ParseError } from 'vs/base/common/json'; import { localize } from 'vs/nls'; @@ -15,14 +15,23 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { URI } from 'vs/base/common/uri'; import { joinPath } from 'vs/base/common/resources'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { startsWith } from 'vs/base/common/strings'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { mergeKeybindings } from 'vs/platform/userDataSync/common/keybindingsMerge'; +interface ISyncPreviewResult { + readonly fileContent: IFileContent | null; + readonly remoteUserData: IUserData; + readonly hasLocalChanged: boolean; + readonly hasRemoteChanged: boolean; + readonly hasConflicts: boolean; +} export class KeybindingsSynchroniser extends Disposable implements ISynchroniser { private static EXTERNAL_USER_DATA_KEYBINDINGS_KEY: string = 'keybindings'; + private syncPreviewResultPromise: CancelablePromise | null = null; + private _status: SyncStatus = SyncStatus.Idle; get status(): SyncStatus { return this._status; } private _onDidChangStatus: Emitter = this._register(new Emitter()); @@ -69,12 +78,17 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser } } - async sync(): Promise { + async sync(_continue?: boolean): Promise { if (!this.configurationService.getValue('sync.enableKeybindings')) { this.logService.trace('Keybindings: Skipping synchronizing keybindings as it is disabled.'); return false; } + if (_continue) { + this.logService.info('Keybindings: Resumed synchronizing keybindings'); + return this.continueSync(); + } + if (this.status !== SyncStatus.Idle) { this.logService.trace('Keybindings: Skipping synchronizing keybindings as it is running already.'); return false; @@ -83,6 +97,152 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser this.logService.trace('Keybindings: Started synchronizing keybindings...'); this.setStatus(SyncStatus.Syncing); + try { + const result = await this.getPreview(); + if (result.hasConflicts) { + this.logService.info('Keybindings: Detected conflicts while synchronizing keybindings.'); + this.setStatus(SyncStatus.HasConflicts); + return false; + } + await this.apply(); + return true; + } catch (e) { + this.syncPreviewResultPromise = null; + this.setStatus(SyncStatus.Idle); + if (e instanceof UserDataSyncStoreError && e.code === UserDataSyncStoreErrorCode.Rejected) { + // Rejected as there is a new remote version. Syncing again, + this.logService.info('Keybindings: Failed to synchronise keybindings as there is a new remote version available. Synchronizing again...'); + return this.sync(); + } + if (e instanceof FileSystemProviderError && e.code === FileSystemProviderErrorCode.FileExists) { + // Rejected as there is a new local version. Syncing again. + this.logService.info('Keybindings: Failed to synchronise keybindings as there is a new local version available. Synchronizing again...'); + return this.sync(); + } + throw e; + } + } + + stop(): void { + if (this.syncPreviewResultPromise) { + this.syncPreviewResultPromise.cancel(); + this.syncPreviewResultPromise = null; + this.logService.info('Keybindings: Stopped synchronizing keybindings.'); + } + this.fileService.del(this.environmentService.keybindingsSyncPreviewResource); + this.setStatus(SyncStatus.Idle); + } + + private async continueSync(): Promise { + if (this.status !== SyncStatus.HasConflicts) { + return false; + } + await this.apply(); + return true; + } + + private async apply(): Promise { + if (!this.syncPreviewResultPromise) { + return; + } + + if (await this.fileService.exists(this.environmentService.keybindingsSyncPreviewResource)) { + const keybindingsPreivew = await this.fileService.readFile(this.environmentService.keybindingsSyncPreviewResource); + const content = keybindingsPreivew.value.toString(); + if (this.hasErrors(content)) { + const error = new Error(localize('errorInvalidKeybindings', "Unable to sync keybindings. Please resolve conflicts without any errors/warnings and try again.")); + this.logService.error(error); + throw error; + } + + let { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged } = await this.syncPreviewResultPromise; + if (!hasLocalChanged && !hasRemoteChanged) { + this.logService.trace('Keybindings: No changes found during synchronizing keybindings.'); + } + if (hasLocalChanged) { + this.logService.info('Keybindings: Updating local keybindings'); + await this.writeToLocal(content, fileContent); + } + if (hasRemoteChanged) { + this.logService.info('Keybindings: Updating remote keybindings'); + const ref = await this.writeToRemote(content, remoteUserData.ref); + remoteUserData = { ref, content }; + } + if (remoteUserData.content) { + this.logService.info('Keybindings: Updating last synchronised keybindings'); + await this.updateLastSyncValue(remoteUserData); + } + + // Delete the preview + await this.fileService.del(this.environmentService.keybindingsSyncPreviewResource); + } else { + this.logService.trace('Keybindings: No changes found during synchronizing keybindings.'); + } + + this.logService.trace('Keybindings: Finised synchronizing keybindings.'); + this.syncPreviewResultPromise = null; + this.setStatus(SyncStatus.Idle); + } + + private hasErrors(content: string): boolean { + const parseErrors: ParseError[] = []; + parse(content, parseErrors, { allowEmptyContent: true, allowTrailingComma: true }); + return parseErrors.length > 0; + } + + private getPreview(): Promise { + if (!this.syncPreviewResultPromise) { + this.syncPreviewResultPromise = createCancelablePromise(token => this.generatePreview(token)); + } + return this.syncPreviewResultPromise; + } + + private async generatePreview(token: CancellationToken): Promise { + const lastSyncData = await this.getLastSyncUserData(); + const remoteUserData = await this.userDataSyncStoreService.read(KeybindingsSynchroniser.EXTERNAL_USER_DATA_KEYBINDINGS_KEY, lastSyncData); + const remoteContent: string | null = remoteUserData.content; + // Get file content last to get the latest + const fileContent = await this.getLocalFileContent(); + let hasLocalChanged: boolean = false; + let hasRemoteChanged: boolean = false; + let hasConflicts: boolean = false; + let previewContent = null; + + if (remoteContent) { + const localContent: string = fileContent ? fileContent.value.toString() : '{}'; + if (this.hasErrors(localContent)) { + this.logService.error('Keybindings: Unable to sync keybindings as there are errors/warning in keybindings file.'); + return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts }; + } + + if (!lastSyncData // First time sync + || lastSyncData.content !== localContent // Local has forwarded + || lastSyncData.content !== remoteContent // Remote has forwarded + ) { + this.logService.trace('Keybindings: Merging remote keybindings with local keybindings...'); + const result = mergeKeybindings(localContent, remoteContent, lastSyncData ? lastSyncData.content : null); + // Sync only if there are changes + if (result.hasChanges) { + hasLocalChanged = result.mergeContent !== localContent; + hasRemoteChanged = result.mergeContent !== remoteContent; + hasConflicts = result.hasConflicts; + previewContent = result.mergeContent; + } + } + } + + // First time syncing to remote + else if (fileContent) { + this.logService.info('Keybindings: Remote keybindings does not exist. Synchronizing keybindings for the first time.'); + hasRemoteChanged = true; + previewContent = fileContent.value.toString(); + } + + if (previewContent && !token.isCancellationRequested) { + await this.fileService.writeFile(this.environmentService.keybindingsSyncPreviewResource, VSBuffer.fromString(previewContent)); + } + + return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts }; } private async getLastSyncUserData(): Promise { @@ -96,7 +256,7 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser private async getLocalFileContent(): Promise { try { - return await this.fileService.readFile(this.environmentService.settingsResource); + return await this.fileService.readFile(this.environmentService.keybindingsResource); } catch (error) { return null; } @@ -109,14 +269,14 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser private async writeToLocal(newContent: string, oldContent: IFileContent | null): Promise { if (oldContent) { // file exists already - await this.fileService.writeFile(this.environmentService.settingsResource, VSBuffer.fromString(newContent), oldContent); + await this.fileService.writeFile(this.environmentService.keybindingsResource, VSBuffer.fromString(newContent), oldContent); } else { // file does not exist - await this.fileService.createFile(this.environmentService.settingsResource, VSBuffer.fromString(newContent), { overwrite: false }); + await this.fileService.createFile(this.environmentService.keybindingsResource, VSBuffer.fromString(newContent), { overwrite: false }); } } private async updateLastSyncValue(remoteUserData: IUserData): Promise { - await this.fileService.writeFile(this.lastSyncSettingsResource, VSBuffer.fromString(JSON.stringify(remoteUserData))); + await this.fileService.writeFile(this.lastSyncKeybindingsResource, VSBuffer.fromString(JSON.stringify(remoteUserData))); } } diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index 96636df1a35..ecefa974966 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -135,10 +135,9 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { } private async continueSync(): Promise { - if (this.status !== SyncStatus.HasConflicts) { - return false; + if (this.status === SyncStatus.HasConflicts) { + await this.apply(); } - await this.apply(); return true; } diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index 3c0598d5e17..c17f0f7746d 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -52,6 +52,12 @@ export function registerConfiguration(): IDisposable { default: true, scope: ConfigurationScope.APPLICATION, }, + 'sync.enableKeybindings': { + type: 'boolean', + description: localize('sync.enableKeybindings', "Enable synchronizing keybindings."), + default: true, + scope: ConfigurationScope.APPLICATION, + }, 'sync.ignoredExtensions': { 'type': 'array', description: localize('sync.ignoredExtensions', "Configure extensions to be ignored while synchronizing."), @@ -132,6 +138,7 @@ export interface ISyncExtension { export const enum SyncSource { Settings = 1, + Keybindings, Extensions } diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index 68935078c44..d4a1b83deda 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -13,6 +13,7 @@ import { timeout } from 'vs/base/common/async'; import { ExtensionsSynchroniser } from 'vs/platform/userDataSync/common/extensionsSync'; import { IExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth'; +import { KeybindingsSynchroniser } from 'vs/platform/userDataSync/common/keybindingsSync'; export class UserDataSyncService extends Disposable implements IUserDataSyncService { @@ -31,6 +32,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ get conflictsSource(): SyncSource | null { return this._conflictsSource; } private readonly settingsSynchroniser: SettingsSynchroniser; + private readonly keybindingsSynchroniser: KeybindingsSynchroniser; private readonly extensionsSynchroniser: ExtensionsSynchroniser; constructor( @@ -40,8 +42,9 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ ) { super(); this.settingsSynchroniser = this._register(this.instantiationService.createInstance(SettingsSynchroniser)); + this.keybindingsSynchroniser = this._register(this.instantiationService.createInstance(KeybindingsSynchroniser)); this.extensionsSynchroniser = this._register(this.instantiationService.createInstance(ExtensionsSynchroniser)); - this.synchronisers = [this.settingsSynchroniser, this.extensionsSynchroniser]; + this.synchronisers = [this.settingsSynchroniser, this.keybindingsSynchroniser, this.extensionsSynchroniser]; this.updateStatus(); if (this.userDataSyncStoreService.userDataSyncStore) { @@ -111,6 +114,9 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ if (source instanceof SettingsSynchroniser) { return SyncSource.Settings; } + if (source instanceof KeybindingsSynchroniser) { + return SyncSource.Keybindings; + } } return null; } diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 98127ebb8c3..cda422c22b8 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -189,6 +189,9 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo const items = [{ id: 'sync.enableSettings', label: localize('user settings', "User Settings") + }, { + id: 'sync.enableKeybindings', + label: localize('user keybindings', "User Keybindings") }, { id: 'sync.enableExtensions', label: localize('extensions', "Extensions") @@ -251,13 +254,14 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } private getPreviewEditorInput(): IEditorInput | undefined { - return this.editorService.editors.filter(input => isEqual(input.getResource(), this.workbenchEnvironmentService.settingsSyncPreviewResource))[0]; + return this.editorService.editors.filter(input => isEqual(input.getResource(), this.workbenchEnvironmentService.settingsSyncPreviewResource) || isEqual(input.getResource(), this.workbenchEnvironmentService.keybindingsSyncPreviewResource))[0]; } private async handleConflicts(): Promise { - if (this.userDataSyncService.conflictsSource === SyncSource.Settings) { + const conflictsResource = this.getConflictsResource(); + if (conflictsResource) { const resourceInput = { - resource: this.workbenchEnvironmentService.settingsSyncPreviewResource, + resource: conflictsResource, options: { preserveFocus: false, pinned: false, @@ -279,6 +283,16 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } } + private getConflictsResource(): URI | null { + if (this.userDataSyncService.conflictsSource === SyncSource.Settings) { + return this.workbenchEnvironmentService.settingsSyncPreviewResource; + } + if (this.userDataSyncService.conflictsSource === SyncSource.Keybindings) { + return this.workbenchEnvironmentService.keybindingsSyncPreviewResource; + } + return null; + } + private registerActions(): void { const startSyncMenuItem: IMenuItem = { @@ -380,6 +394,19 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo order: 1, when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.isEqualTo(SyncStatus.HasConflicts), ResourceContextKey.Resource.isEqualTo(this.workbenchEnvironmentService.settingsSyncPreviewResource.toString())), }); + MenuRegistry.appendMenuItem(MenuId.EditorTitle, { + command: { + id: continueSyncCommandId, + title: localize('continue sync', "Sync: Continue"), + iconLocation: { + light: SYNC_PUSH_LIGHT_ICON_URI, + dark: SYNC_PUSH_DARK_ICON_URI + } + }, + group: 'navigation', + order: 1, + when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.isEqualTo(SyncStatus.HasConflicts), ResourceContextKey.Resource.isEqualTo(this.workbenchEnvironmentService.keybindingsSyncPreviewResource.toString())), + }); const signOutMenuItem: IMenuItem = { group: '5_sync', diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index bdbed598a77..d54e68fa70f 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -136,6 +136,9 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment @memoize get settingsSyncPreviewResource(): URI { return joinPath(this.userRoamingDataHome, '.settings.json'); } + @memoize + get keybindingsSyncPreviewResource(): URI { return joinPath(this.userRoamingDataHome, '.keybindings.json'); } + @memoize get userDataSyncLogResource(): URI { return joinPath(this.options.logsPath, 'userDataSync.log'); } diff --git a/src/vs/workbench/services/userDataSync/common/settingsMergeService.ts b/src/vs/workbench/services/userDataSync/common/settingsMergeService.ts index 973a7fd7a68..bf2eb9e6dc0 100644 --- a/src/vs/workbench/services/userDataSync/common/settingsMergeService.ts +++ b/src/vs/workbench/services/userDataSync/common/settingsMergeService.ts @@ -156,7 +156,7 @@ class SettingsMergeService implements ISettingsMergeService { const remote = parse(remoteContent); const remoteModel = this.modelService.createModel(localContent, this.modeService.create('jsonc')); const ignored = ignoredSettings.reduce((set, key) => { set.add(key); return set; }, new Set()); - for (const key of Object.keys(ignoredSettings)) { + for (const key of ignoredSettings) { if (ignored.has(key)) { this.editSetting(remoteModel, key, undefined); this.editSetting(remoteModel, key, remote[key]); From 198a94aec7da76d216923b19806dbf8327db028d Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 25 Nov 2019 12:22:25 +0100 Subject: [PATCH 034/255] add more tests and fixes --- .../userDataSync/common/keybindingsMerge.ts | 2 +- .../test/keybindingsMerge.test.ts | 46 ++++++++++++++++++- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/src/vs/platform/userDataSync/common/keybindingsMerge.ts b/src/vs/platform/userDataSync/common/keybindingsMerge.ts index 731a1309b39..4651bec300b 100644 --- a/src/vs/platform/userDataSync/common/keybindingsMerge.ts +++ b/src/vs/platform/userDataSync/common/keybindingsMerge.ts @@ -150,11 +150,11 @@ export function mergeKeybindings(localContent: string, remoteContent: string, ba const firstNode = findNodeAtLocation(tree, [firstIndex])!; const startLocalOffset = contentUtil.getLineStartOffset(mergeContent, eol, firstNode.offset) - eol.length; let endLocalOffset = startLocalOffset; - let remoteOffset = endLocalOffset; if (local) { const lastLocalValueNode = findNodeAtLocation(tree, [firstIndex + local.length - 1])!; endLocalOffset = contentUtil.getLineEndOffset(mergeContent, eol, lastLocalValueNode.offset + lastLocalValueNode.length); } + let remoteOffset = endLocalOffset; if (remote) { const lastRemoteValueNode = findNodeAtLocation(tree, [firstIndex + (local ? local.length : 0) + remote.length - 1])!; remoteOffset = contentUtil.getLineEndOffset(mergeContent, eol, lastRemoteValueNode.offset + lastRemoteValueNode.length); diff --git a/src/vs/platform/userDataSync/test/keybindingsMerge.test.ts b/src/vs/platform/userDataSync/test/keybindingsMerge.test.ts index 25a2fb0d116..d1e8d85a41b 100644 --- a/src/vs/platform/userDataSync/test/keybindingsMerge.test.ts +++ b/src/vs/platform/userDataSync/test/keybindingsMerge.test.ts @@ -546,8 +546,6 @@ suite('KeybindingsMerge - Conflicts', () => { const localContent = stringify([]); const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); const actual = mergeKeybindings(localContent, remoteContent, baseContent); - //'[\n<<<<<<< local\n\n=======\t{\n\t\t"key": "alt+c",\n\t\t"command": "a",\n\t\t"when": "editorTextFocus && !editorReadonly"\n\t}\n>>>>>>> remote\n]' - //'[\n<<<<<<< local\n=======\n\t{\n\t\t"key": "alt+c",\n\t\t"command": "a",\n\t\t"when": "editorTextFocus && !editorReadonly"\n\t}\n>>>>>>> remote\n]' assert.ok(actual.hasChanges); assert.ok(actual.hasConflicts); assert.equal(actual.mergeContent, @@ -587,6 +585,50 @@ suite('KeybindingsMerge - Conflicts', () => { ]`); }); + test('merge when the entry is removed in remote but updated in local', () => { + const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const remoteContent = stringify([]); + const actual = mergeKeybindings(localContent, remoteContent, baseContent); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `[ +<<<<<<< local + { + "key": "alt+c", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + } +======= +>>>>>>> remote +]`); + }); + + test('merge when the entry is removed in remote but updated in local and a new entry is added in remote', () => { + const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const remoteContent = stringify([{ key: 'alt+b', command: 'b' }]); + const actual = mergeKeybindings(localContent, remoteContent, baseContent); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `[ +<<<<<<< local + { + "key": "alt+c", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + }, +======= +>>>>>>> remote + { + "key": "alt+b", + "command": "b" + } +]`); + }); + }); function stringify(value: any): any { From 3f26fb90a3f03fbeea6721484266755e21dd3574 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 25 Nov 2019 13:01:33 +0100 Subject: [PATCH 035/255] :lipstick: --- .../userDataSync/common/keybindingsSync.ts | 48 ++++++++++--------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index b71b73c844e..3af6ea8768c 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -57,7 +57,7 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser } private async onDidChangeKeybindings(): Promise { - const localFileContent = await this.getLocalFileContent(); + const localFileContent = await this.getLocalContent(); const lastSyncData = await this.getLastSyncUserData(); if (localFileContent && lastSyncData) { if (localFileContent.value.toString() !== lastSyncData.content) { @@ -161,16 +161,16 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser } if (hasLocalChanged) { this.logService.info('Keybindings: Updating local keybindings'); - await this.writeToLocal(content, fileContent); + await this.updateLocalContent(content, fileContent); } if (hasRemoteChanged) { this.logService.info('Keybindings: Updating remote keybindings'); - const ref = await this.writeToRemote(content, remoteUserData.ref); + const ref = await this.updateRemoteContent(content, remoteUserData.ref); remoteUserData = { ref, content }; } if (remoteUserData.content) { this.logService.info('Keybindings: Updating last synchronised keybindings'); - await this.updateLastSyncValue(remoteUserData); + await this.updateLastSyncUserData(remoteUserData); } // Delete the preview @@ -199,10 +199,10 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser private async generatePreview(token: CancellationToken): Promise { const lastSyncData = await this.getLastSyncUserData(); - const remoteUserData = await this.userDataSyncStoreService.read(KeybindingsSynchroniser.EXTERNAL_USER_DATA_KEYBINDINGS_KEY, lastSyncData); + const remoteUserData = await this.getRemoteContent(lastSyncData); const remoteContent: string | null = remoteUserData.content; // Get file content last to get the latest - const fileContent = await this.getLocalFileContent(); + const fileContent = await this.getLocalContent(); let hasLocalChanged: boolean = false; let hasRemoteChanged: boolean = false; let hasConflicts: boolean = false; @@ -245,16 +245,7 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts }; } - private async getLastSyncUserData(): Promise { - try { - const content = await this.fileService.readFile(this.lastSyncKeybindingsResource); - return JSON.parse(content.value.toString()); - } catch (error) { - return null; - } - } - - private async getLocalFileContent(): Promise { + private async getLocalContent(): Promise { try { return await this.fileService.readFile(this.environmentService.keybindingsResource); } catch (error) { @@ -262,11 +253,7 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser } } - private async writeToRemote(content: string, ref: string | null): Promise { - return this.userDataSyncStoreService.write(KeybindingsSynchroniser.EXTERNAL_USER_DATA_KEYBINDINGS_KEY, content, ref); - } - - private async writeToLocal(newContent: string, oldContent: IFileContent | null): Promise { + private async updateLocalContent(newContent: string, oldContent: IFileContent | null): Promise { if (oldContent) { // file exists already await this.fileService.writeFile(this.environmentService.keybindingsResource, VSBuffer.fromString(newContent), oldContent); @@ -276,7 +263,24 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser } } - private async updateLastSyncValue(remoteUserData: IUserData): Promise { + private async getLastSyncUserData(): Promise { + try { + const content = await this.fileService.readFile(this.lastSyncKeybindingsResource); + return JSON.parse(content.value.toString()); + } catch (error) { + return null; + } + } + + private async updateLastSyncUserData(remoteUserData: IUserData): Promise { await this.fileService.writeFile(this.lastSyncKeybindingsResource, VSBuffer.fromString(JSON.stringify(remoteUserData))); } + + private async getRemoteContent(lastSyncData: IUserData | null): Promise { + return this.userDataSyncStoreService.read(KeybindingsSynchroniser.EXTERNAL_USER_DATA_KEYBINDINGS_KEY, lastSyncData); + } + + private async updateRemoteContent(content: string, ref: string | null): Promise { + return this.userDataSyncStoreService.write(KeybindingsSynchroniser.EXTERNAL_USER_DATA_KEYBINDINGS_KEY, content, ref); + } } From 1fbe309eb3f7e07fceb1ea0ee3a02a553457ee76 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 27 Nov 2019 10:06:54 +0100 Subject: [PATCH 036/255] movw keybindings merge to workbench as service --- .../sharedProcess/sharedProcessMain.ts | 5 +- .../userDataSync/common/keybindingsMerge.ts | 259 ----------------- .../userDataSync/common/keybindingsSync.ts | 6 +- .../userDataSync/common/keybindingsSyncIpc.ts | 37 +++ .../userDataSync.contribution.ts | 5 +- .../keybinding/common/keybindingsMerge.ts | 265 ++++++++++++++++++ .../keybindingsMerge.test.ts | 127 +++++---- src/vs/workbench/workbench.common.main.ts | 1 + 8 files changed, 380 insertions(+), 325 deletions(-) delete mode 100644 src/vs/platform/userDataSync/common/keybindingsMerge.ts create mode 100644 src/vs/platform/userDataSync/common/keybindingsSyncIpc.ts create mode 100644 src/vs/workbench/services/keybinding/common/keybindingsMerge.ts rename src/vs/{platform/userDataSync/test => workbench/services/keybinding/test/electron-browser}/keybindingsMerge.test.ts (83%) diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 1492e7710c1..60766bcc4a5 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -50,7 +50,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { DiskFileSystemProvider } from 'vs/platform/files/electron-browser/diskFileSystemProvider'; import { Schemas } from 'vs/base/common/network'; import { IProductService } from 'vs/platform/product/common/productService'; -import { IUserDataSyncService, IUserDataSyncStoreService, ISettingsMergeService, registerConfiguration, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, IUserDataSyncStoreService, ISettingsMergeService, registerConfiguration, IUserDataSyncLogService, IKeybindingsMergeService } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncService, UserDataAutoSync } from 'vs/platform/userDataSync/common/userDataSyncService'; import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; import { UserDataSyncChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc'; @@ -63,6 +63,7 @@ import { AuthTokenService } from 'vs/platform/auth/electron-browser/authTokenSer import { AuthTokenChannel } from 'vs/platform/auth/common/authTokenIpc'; import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; import { KeytarCredentialsService } from 'vs/platform/credentials/node/credentialsService'; +import { KeybindingsMergeChannelClient } from 'vs/platform/userDataSync/common/keybindingsSyncIpc'; export interface ISharedProcessConfiguration { readonly machineId: string; @@ -186,6 +187,8 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat services.set(IUserDataSyncLogService, new SyncDescriptor(UserDataSyncLogService)); const settingsMergeChannel = server.getChannel('settingsMerge', activeWindowRouter); services.set(ISettingsMergeService, new SettingsMergeChannelClient(settingsMergeChannel)); + const keybindingsMergeChannel = server.getChannel('keybindingsMerge', activeWindowRouter); + services.set(IKeybindingsMergeService, new KeybindingsMergeChannelClient(keybindingsMergeChannel)); services.set(IUserDataSyncStoreService, new SyncDescriptor(UserDataSyncStoreService)); services.set(IUserDataSyncService, new SyncDescriptor(UserDataSyncService)); registerConfiguration(); diff --git a/src/vs/platform/userDataSync/common/keybindingsMerge.ts b/src/vs/platform/userDataSync/common/keybindingsMerge.ts deleted file mode 100644 index 4651bec300b..00000000000 --- a/src/vs/platform/userDataSync/common/keybindingsMerge.ts +++ /dev/null @@ -1,259 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as objects from 'vs/base/common/objects'; -import { parse, findNodeAtLocation, parseTree } from 'vs/base/common/json'; -import { values, keys } from 'vs/base/common/map'; -import { IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding'; -import { firstIndex as findFirstIndex, equals } from 'vs/base/common/arrays'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import * as contentUtil from 'vs/platform/userDataSync/common/content'; - -export function mergeKeybindings(localContent: string, remoteContent: string, baseContent: string | null): { mergeContent: string, hasChanges: boolean, hasConflicts: boolean } { - const local = parse(localContent); - const remote = parse(remoteContent); - const base = baseContent ? parse(baseContent) : null; - - const byCommand = (keybindings: IUserFriendlyKeybinding[]) => { - const map: Map = new Map(); - for (const keybinding of keybindings) { - const command = keybinding.command[0] === '-' ? keybinding.command.substring(1) : keybinding.command; - let value = map.get(command); - if (!value) { - value = []; - map.set(command, value); - } - value.push(keybinding); - } - return map; - }; - - const localByCommand = byCommand(local); - const remoteByCommand = byCommand(remote); - const baseByCommand = base ? byCommand(base) : null; - - const localToRemote = compare(localByCommand, remoteByCommand); - if (localToRemote.added.size === 0 && localToRemote.removed.size === 0 && localToRemote.updated.size === 0) { - // No changes found between local and remote. - return { mergeContent: localContent, hasChanges: false, hasConflicts: false }; - } - - const conflictCommands: Set = new Set(); - const baseToLocal = baseByCommand ? compare(baseByCommand, localByCommand) : { added: keys(localByCommand).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; - const baseToRemote = baseByCommand ? compare(baseByCommand, remoteByCommand) : { added: keys(remoteByCommand).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; - const eol = contentUtil.getEol(localContent); - let mergeContent = localContent; - - // Removed commands in Local - for (const command of values(baseToLocal.removed)) { - // Got updated in remote - if (baseToRemote.updated.has(command)) { - conflictCommands.add(command); - } - } - - // Removed commands in Remote - for (const command of values(baseToRemote.removed)) { - if (conflictCommands.has(command)) { - continue; - } - // Got updated in local - if (baseToLocal.updated.has(command)) { - conflictCommands.add(command); - } else { - // remove the command - mergeContent = removeKeybindings(mergeContent, eol, command); - } - } - - // Added commands in Local - for (const command of values(baseToLocal.added)) { - if (conflictCommands.has(command)) { - continue; - } - // Got added in remote - if (baseToRemote.added.has(command)) { - // Has different value - if (localToRemote.updated.has(command)) { - conflictCommands.add(command); - } - } - } - - // Added commands in remote - for (const command of values(baseToRemote.added)) { - if (conflictCommands.has(command)) { - continue; - } - // Got added in local - if (baseToLocal.added.has(command)) { - // Has different value - if (localToRemote.updated.has(command)) { - conflictCommands.add(command); - } - } else { - mergeContent = addKeybinding(mergeContent, eol, command, remoteByCommand.get(command)!); - } - } - - // Updated commands in Local - for (const command of values(baseToLocal.updated)) { - if (conflictCommands.has(command)) { - continue; - } - // Got updated in remote - if (baseToRemote.updated.has(command)) { - // Has different value - if (localToRemote.updated.has(command)) { - conflictCommands.add(command); - } - } - } - - // Updated commands in Remote - for (const command of values(baseToRemote.updated)) { - if (conflictCommands.has(command)) { - continue; - } - // Got updated in local - if (baseToLocal.updated.has(command)) { - // Has different value - if (localToRemote.updated.has(command)) { - conflictCommands.add(command); - } - } else { - // update the command - mergeContent = updateKeybinding(mergeContent, eol, command, remoteByCommand.get(command)!); - } - } - - const conflicts: { command: string, local: IUserFriendlyKeybinding[] | undefined, remote: IUserFriendlyKeybinding[] | undefined, firstIndex: number }[] = []; - - for (const command of values(conflictCommands)) { - const local = localByCommand.get(command); - const remote = remoteByCommand.get(command); - mergeContent = updateKeybinding(mergeContent, eol, command, [...local || [], ...remote || []]); - conflicts.push({ command, local, remote, firstIndex: -1 }); - } - - const allKeybindings = parse(mergeContent); - for (const conflict of conflicts) { - conflict.firstIndex = findFirstIndex(allKeybindings, keybinding => keybinding.command === conflict.command || keybinding.command === `-${conflict.command}`); - } - // Sort reverse so that conflicts content is added from last - conflicts.sort((a, b) => b.firstIndex - a.firstIndex); - - const tree = parseTree(mergeContent); - for (const { firstIndex, local, remote } of conflicts) { - const firstNode = findNodeAtLocation(tree, [firstIndex])!; - const startLocalOffset = contentUtil.getLineStartOffset(mergeContent, eol, firstNode.offset) - eol.length; - let endLocalOffset = startLocalOffset; - if (local) { - const lastLocalValueNode = findNodeAtLocation(tree, [firstIndex + local.length - 1])!; - endLocalOffset = contentUtil.getLineEndOffset(mergeContent, eol, lastLocalValueNode.offset + lastLocalValueNode.length); - } - let remoteOffset = endLocalOffset; - if (remote) { - const lastRemoteValueNode = findNodeAtLocation(tree, [firstIndex + (local ? local.length : 0) + remote.length - 1])!; - remoteOffset = contentUtil.getLineEndOffset(mergeContent, eol, lastRemoteValueNode.offset + lastRemoteValueNode.length); - } - mergeContent = mergeContent.substring(0, startLocalOffset) - + `${eol}<<<<<<< local` - + mergeContent.substring(startLocalOffset, endLocalOffset) - + `${eol}=======` - + mergeContent.substring(endLocalOffset, remoteOffset) - + `${eol}>>>>>>> remote` - + mergeContent.substring(remoteOffset); - } - - return { mergeContent, hasChanges: true, hasConflicts: conflicts.length > 0 }; -} - -function compare(from: Map, to: Map): { added: Set, removed: Set, updated: Set } { - const fromKeys = keys(from); - const toKeys = keys(to); - const added = toKeys.filter(key => fromKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); - const removed = fromKeys.filter(key => toKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); - const updated: Set = new Set(); - - for (const key of fromKeys) { - if (removed.has(key)) { - continue; - } - const value1: IUserFriendlyKeybinding[] = from.get(key)!; - const value2: IUserFriendlyKeybinding[] = to.get(key)!; - if (!areSameKeybindings(value1, value2)) { - updated.add(key); - } - } - - return { added, removed, updated }; -} - -function areSameKeybindings(value1: IUserFriendlyKeybinding[], value2: IUserFriendlyKeybinding[]): boolean { - // Compare entries adding keybindings - if (!equals(value1.filter(({ command }) => command[0] !== '-'), value2.filter(({ command }) => command[0] !== '-'), (a, b) => isSameKeybinding(a, b))) { - return false; - } - // Compare entries removing keybindings - if (!equals(value1.filter(({ command }) => command[0] === '-'), value2.filter(({ command }) => command[0] === '-'), (a, b) => isSameKeybinding(a, b))) { - return false; - } - return true; -} - -function isSameKeybinding(a: IUserFriendlyKeybinding, b: IUserFriendlyKeybinding): boolean { - if (a.command !== b.command) { - return false; - } - if (a.key !== b.key) { - return false; - } - const whenA = ContextKeyExpr.deserialize(a.when); - const whenB = ContextKeyExpr.deserialize(b.when); - if ((whenA && !whenB) || (!whenA && whenB)) { - return false; - } - if (whenA && whenB && !whenA.equals(whenB)) { - return false; - } - if (!objects.equals(a.args, b.args)) { - return false; - } - return true; -} - -function addKeybinding(content: string, eol: string, command: string, keybindings: IUserFriendlyKeybinding[]): string { - for (const keybinding of keybindings) { - content = contentUtil.edit(content, eol, [-1], keybinding); - } - return content; -} - -function removeKeybindings(content: string, eol: string, command: string): string { - const keybindings = parse(content); - for (let index = keybindings.length - 1; index >= 0; index--) { - if (keybindings[index].command === command || keybindings[index].command === `-${command}`) { - content = contentUtil.edit(content, eol, [index], undefined); - } - } - return content; -} - -function updateKeybinding(content: string, eol: string, command: string, keybindings: IUserFriendlyKeybinding[]): string { - const allKeybindings = parse(content); - const location = findFirstIndex(allKeybindings, keybinding => keybinding.command === command || keybinding.command === `-${command}`); - // Remove all entries with this command - for (let index = allKeybindings.length - 1; index >= 0; index--) { - if (allKeybindings[index].command === command || allKeybindings[index].command === `-${command}`) { - content = contentUtil.edit(content, eol, [index], undefined); - } - } - // add all entries at the same location where the entry with this command was located. - for (let index = keybindings.length - 1; index >= 0; index--) { - content = contentUtil.edit(content, eol, [location], keybindings[index]); - } - return content; -} diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index 3af6ea8768c..4da5fadbe44 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -5,7 +5,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IFileService, FileSystemProviderErrorCode, FileSystemProviderError, IFileContent } from 'vs/platform/files/common/files'; -import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, ISynchroniser, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, ISynchroniser, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IKeybindingsMergeService } from 'vs/platform/userDataSync/common/userDataSync'; import { VSBuffer } from 'vs/base/common/buffer'; import { parse, ParseError } from 'vs/base/common/json'; import { localize } from 'vs/nls'; @@ -16,7 +16,6 @@ import { URI } from 'vs/base/common/uri'; import { joinPath } from 'vs/base/common/resources'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { mergeKeybindings } from 'vs/platform/userDataSync/common/keybindingsMerge'; interface ISyncPreviewResult { readonly fileContent: IFileContent | null; @@ -49,6 +48,7 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser @IConfigurationService private readonly configurationService: IConfigurationService, @IFileService private readonly fileService: IFileService, @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IKeybindingsMergeService private readonly keybindingsMergeService: IKeybindingsMergeService, ) { super(); this.lastSyncKeybindingsResource = joinPath(this.environmentService.userRoamingDataHome, '.lastSyncKeybindings.json'); @@ -220,7 +220,7 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser || lastSyncData.content !== remoteContent // Remote has forwarded ) { this.logService.trace('Keybindings: Merging remote keybindings with local keybindings...'); - const result = mergeKeybindings(localContent, remoteContent, lastSyncData ? lastSyncData.content : null); + const result = await this.keybindingsMergeService.merge(localContent, remoteContent, lastSyncData ? lastSyncData.content : null); // Sync only if there are changes if (result.hasChanges) { hasLocalChanged = result.mergeContent !== localContent; diff --git a/src/vs/platform/userDataSync/common/keybindingsSyncIpc.ts b/src/vs/platform/userDataSync/common/keybindingsSyncIpc.ts new file mode 100644 index 00000000000..0bfa0de8f6a --- /dev/null +++ b/src/vs/platform/userDataSync/common/keybindingsSyncIpc.ts @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; +import { Event } from 'vs/base/common/event'; +import { IKeybindingsMergeService } from 'vs/platform/userDataSync/common/userDataSync'; + +export class KeybindingsMergeChannel implements IServerChannel { + + constructor(private readonly service: IKeybindingsMergeService) { } + + listen(_: unknown, event: string): Event { + throw new Error(`Event not found: ${event}`); + } + + call(context: any, command: string, args?: any): Promise { + switch (command) { + case 'merge': return this.service.merge(args[0], args[1], args[2]); + } + throw new Error('Invalid call'); + } +} + +export class KeybindingsMergeChannelClient implements IKeybindingsMergeService { + + _serviceBrand: undefined; + + constructor(private readonly channel: IChannel) { + } + + merge(localContent: string, remoteContent: string, baseContent: string | null): Promise<{ mergeContent: string, hasChanges: boolean, hasConflicts: boolean }> { + return this.channel.call('merge', [localContent, remoteContent, baseContent]); + } + +} diff --git a/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts b/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts index 2b8fb68f933..9a07beb2623 100644 --- a/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts +++ b/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts @@ -4,19 +4,22 @@ *--------------------------------------------------------------------------------------------*/ import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { ISettingsMergeService } from 'vs/platform/userDataSync/common/userDataSync'; +import { ISettingsMergeService, IKeybindingsMergeService } from 'vs/platform/userDataSync/common/userDataSync'; import { Registry } from 'vs/platform/registry/common/platform'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { SettingsMergeChannel } from 'vs/platform/userDataSync/common/settingsSyncIpc'; +import { KeybindingsMergeChannel } from 'vs/platform/userDataSync/common/keybindingsSyncIpc'; class UserDataSyncServicesContribution implements IWorkbenchContribution { constructor( @ISettingsMergeService settingsMergeService: ISettingsMergeService, + @IKeybindingsMergeService keybindingsMergeService: IKeybindingsMergeService, @ISharedProcessService sharedProcessService: ISharedProcessService, ) { sharedProcessService.registerChannel('settingsMerge', new SettingsMergeChannel(settingsMergeService)); + sharedProcessService.registerChannel('keybindingsMerge', new KeybindingsMergeChannel(keybindingsMergeService)); } } diff --git a/src/vs/workbench/services/keybinding/common/keybindingsMerge.ts b/src/vs/workbench/services/keybinding/common/keybindingsMerge.ts new file mode 100644 index 00000000000..fda747c4861 --- /dev/null +++ b/src/vs/workbench/services/keybinding/common/keybindingsMerge.ts @@ -0,0 +1,265 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as objects from 'vs/base/common/objects'; +import { parse, findNodeAtLocation, parseTree } from 'vs/base/common/json'; +import { values, keys } from 'vs/base/common/map'; +import { IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding'; +import { firstIndex as findFirstIndex, equals } from 'vs/base/common/arrays'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import * as contentUtil from 'vs/platform/userDataSync/common/content'; +import { IKeybindingsMergeService } from 'vs/platform/userDataSync/common/userDataSync'; + +export class KeybindingsMergeService implements IKeybindingsMergeService { + + _serviceBrand: undefined; + + public async merge(localContent: string, remoteContent: string, baseContent: string | null): Promise<{ mergeContent: string, hasChanges: boolean, hasConflicts: boolean }> { + const local = parse(localContent); + const remote = parse(remoteContent); + const base = baseContent ? parse(baseContent) : null; + + const byCommand = (keybindings: IUserFriendlyKeybinding[]) => { + const map: Map = new Map(); + for (const keybinding of keybindings) { + const command = keybinding.command[0] === '-' ? keybinding.command.substring(1) : keybinding.command; + let value = map.get(command); + if (!value) { + value = []; + map.set(command, value); + } + value.push(keybinding); + } + return map; + }; + + const localByCommand = byCommand(local); + const remoteByCommand = byCommand(remote); + const baseByCommand = base ? byCommand(base) : null; + + const localToRemote = this.compare(localByCommand, remoteByCommand); + if (localToRemote.added.size === 0 && localToRemote.removed.size === 0 && localToRemote.updated.size === 0) { + // No changes found between local and remote. + return { mergeContent: localContent, hasChanges: false, hasConflicts: false }; + } + + const conflictCommands: Set = new Set(); + const baseToLocal = baseByCommand ? this.compare(baseByCommand, localByCommand) : { added: keys(localByCommand).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; + const baseToRemote = baseByCommand ? this.compare(baseByCommand, remoteByCommand) : { added: keys(remoteByCommand).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; + const eol = contentUtil.getEol(localContent); + let mergeContent = localContent; + + // Removed commands in Local + for (const command of values(baseToLocal.removed)) { + // Got updated in remote + if (baseToRemote.updated.has(command)) { + conflictCommands.add(command); + } + } + + // Removed commands in Remote + for (const command of values(baseToRemote.removed)) { + if (conflictCommands.has(command)) { + continue; + } + // Got updated in local + if (baseToLocal.updated.has(command)) { + conflictCommands.add(command); + } else { + // remove the command + mergeContent = this.removeKeybindings(mergeContent, eol, command); + } + } + + // Added commands in Local + for (const command of values(baseToLocal.added)) { + if (conflictCommands.has(command)) { + continue; + } + // Got added in remote + if (baseToRemote.added.has(command)) { + // Has different value + if (localToRemote.updated.has(command)) { + conflictCommands.add(command); + } + } + } + + // Added commands in remote + for (const command of values(baseToRemote.added)) { + if (conflictCommands.has(command)) { + continue; + } + // Got added in local + if (baseToLocal.added.has(command)) { + // Has different value + if (localToRemote.updated.has(command)) { + conflictCommands.add(command); + } + } else { + mergeContent = this.addKeybinding(mergeContent, eol, command, remoteByCommand.get(command)!); + } + } + + // Updated commands in Local + for (const command of values(baseToLocal.updated)) { + if (conflictCommands.has(command)) { + continue; + } + // Got updated in remote + if (baseToRemote.updated.has(command)) { + // Has different value + if (localToRemote.updated.has(command)) { + conflictCommands.add(command); + } + } + } + + // Updated commands in Remote + for (const command of values(baseToRemote.updated)) { + if (conflictCommands.has(command)) { + continue; + } + // Got updated in local + if (baseToLocal.updated.has(command)) { + // Has different value + if (localToRemote.updated.has(command)) { + conflictCommands.add(command); + } + } else { + // update the command + mergeContent = this.updateKeybinding(mergeContent, eol, command, remoteByCommand.get(command)!); + } + } + + const conflicts: { command: string, local: IUserFriendlyKeybinding[] | undefined, remote: IUserFriendlyKeybinding[] | undefined, firstIndex: number }[] = []; + + for (const command of values(conflictCommands)) { + const local = localByCommand.get(command); + const remote = remoteByCommand.get(command); + mergeContent = this.updateKeybinding(mergeContent, eol, command, [...local || [], ...remote || []]); + conflicts.push({ command, local, remote, firstIndex: -1 }); + } + + const allKeybindings = parse(mergeContent); + for (const conflict of conflicts) { + conflict.firstIndex = findFirstIndex(allKeybindings, keybinding => keybinding.command === conflict.command || keybinding.command === `-${conflict.command}`); + } + // Sort reverse so that conflicts content is added from last + conflicts.sort((a, b) => b.firstIndex - a.firstIndex); + + const tree = parseTree(mergeContent); + for (const { firstIndex, local, remote } of conflicts) { + const firstNode = findNodeAtLocation(tree, [firstIndex])!; + const startLocalOffset = contentUtil.getLineStartOffset(mergeContent, eol, firstNode.offset) - eol.length; + let endLocalOffset = startLocalOffset; + if (local) { + const lastLocalValueNode = findNodeAtLocation(tree, [firstIndex + local.length - 1])!; + endLocalOffset = contentUtil.getLineEndOffset(mergeContent, eol, lastLocalValueNode.offset + lastLocalValueNode.length); + } + let remoteOffset = endLocalOffset; + if (remote) { + const lastRemoteValueNode = findNodeAtLocation(tree, [firstIndex + (local ? local.length : 0) + remote.length - 1])!; + remoteOffset = contentUtil.getLineEndOffset(mergeContent, eol, lastRemoteValueNode.offset + lastRemoteValueNode.length); + } + mergeContent = mergeContent.substring(0, startLocalOffset) + + `${eol}<<<<<<< local` + + mergeContent.substring(startLocalOffset, endLocalOffset) + + `${eol}=======` + + mergeContent.substring(endLocalOffset, remoteOffset) + + `${eol}>>>>>>> remote` + + mergeContent.substring(remoteOffset); + } + + return { mergeContent, hasChanges: true, hasConflicts: conflicts.length > 0 }; + } + + private compare(from: Map, to: Map): { added: Set, removed: Set, updated: Set } { + const fromKeys = keys(from); + const toKeys = keys(to); + const added = toKeys.filter(key => fromKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); + const removed = fromKeys.filter(key => toKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); + const updated: Set = new Set(); + + for (const key of fromKeys) { + if (removed.has(key)) { + continue; + } + const value1: IUserFriendlyKeybinding[] = from.get(key)!; + const value2: IUserFriendlyKeybinding[] = to.get(key)!; + if (!this.areSameKeybindings(value1, value2)) { + updated.add(key); + } + } + + return { added, removed, updated }; + } + + private areSameKeybindings(value1: IUserFriendlyKeybinding[], value2: IUserFriendlyKeybinding[]): boolean { + // Compare entries adding keybindings + if (!equals(value1.filter(({ command }) => command[0] !== '-'), value2.filter(({ command }) => command[0] !== '-'), (a, b) => this.isSameKeybinding(a, b))) { + return false; + } + // Compare entries removing keybindings + if (!equals(value1.filter(({ command }) => command[0] === '-'), value2.filter(({ command }) => command[0] === '-'), (a, b) => this.isSameKeybinding(a, b))) { + return false; + } + return true; + } + + private isSameKeybinding(a: IUserFriendlyKeybinding, b: IUserFriendlyKeybinding): boolean { + if (a.command !== b.command) { + return false; + } + if (a.key !== b.key) { + return false; + } + const whenA = ContextKeyExpr.deserialize(a.when); + const whenB = ContextKeyExpr.deserialize(b.when); + if ((whenA && !whenB) || (!whenA && whenB)) { + return false; + } + if (whenA && whenB && !whenA.equals(whenB)) { + return false; + } + if (!objects.equals(a.args, b.args)) { + return false; + } + return true; + } + + private addKeybinding(content: string, eol: string, command: string, keybindings: IUserFriendlyKeybinding[]): string { + for (const keybinding of keybindings) { + content = contentUtil.edit(content, eol, [-1], keybinding); + } + return content; + } + + private removeKeybindings(content: string, eol: string, command: string): string { + const keybindings = parse(content); + for (let index = keybindings.length - 1; index >= 0; index--) { + if (keybindings[index].command === command || keybindings[index].command === `-${command}`) { + content = contentUtil.edit(content, eol, [index], undefined); + } + } + return content; + } + + private updateKeybinding(content: string, eol: string, command: string, keybindings: IUserFriendlyKeybinding[]): string { + const allKeybindings = parse(content); + const location = findFirstIndex(allKeybindings, keybinding => keybinding.command === command || keybinding.command === `-${command}`); + // Remove all entries with this command + for (let index = allKeybindings.length - 1; index >= 0; index--) { + if (allKeybindings[index].command === command || allKeybindings[index].command === `-${command}`) { + content = contentUtil.edit(content, eol, [index], undefined); + } + } + // add all entries at the same location where the entry with this command was located. + for (let index = keybindings.length - 1; index >= 0; index--) { + content = contentUtil.edit(content, eol, [location], keybindings[index]); + } + return content; + } +} diff --git a/src/vs/platform/userDataSync/test/keybindingsMerge.test.ts b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingsMerge.test.ts similarity index 83% rename from src/vs/platform/userDataSync/test/keybindingsMerge.test.ts rename to src/vs/workbench/services/keybinding/test/electron-browser/keybindingsMerge.test.ts index d1e8d85a41b..3df3c0293f6 100644 --- a/src/vs/platform/userDataSync/test/keybindingsMerge.test.ts +++ b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingsMerge.test.ts @@ -4,29 +4,34 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { mergeKeybindings } from 'vs/platform/userDataSync/common/keybindingsMerge'; +import { workbenchInstantiationService } from 'vs/workbench/test/workbenchTestServices'; +import { KeybindingsMergeService } from 'vs/workbench/services/keybinding/common/keybindingsMerge'; + +let testObject: KeybindingsMergeService; + +suiteSetup(() => testObject = workbenchInstantiationService().createInstance(KeybindingsMergeService)); suite('KeybindingsMerge - No Conflicts', () => { - test('merge when local and remote are same with one entry', () => { + test('merge when local and remote are same with one entry', async () => { const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const actual = mergeKeybindings(localContent, remoteContent, null); + const actual = await testObject.merge(localContent, remoteContent, null); assert.ok(!actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); }); - test('merge when local and remote are same with similar when contexts', () => { + test('merge when local and remote are same with similar when contexts', async () => { const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: '!editorReadonly && editorTextFocus' }]); - const actual = mergeKeybindings(localContent, remoteContent, null); + const actual = await testObject.merge(localContent, remoteContent, null); assert.ok(!actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); }); - test('merge when local and remote are same with multiple entries', () => { + test('merge when local and remote are same with multiple entries', async () => { const localContent = stringify([ { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, @@ -37,13 +42,13 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+d', command: '-a' }, { key: 'cmd+c', command: 'b', args: { text: '`' } } ]); - const actual = mergeKeybindings(localContent, remoteContent, null); + const actual = await testObject.merge(localContent, remoteContent, null); assert.ok(!actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); }); - test('merge when local and remote are same with different base content', () => { + test('merge when local and remote are same with different base content', async () => { const localContent = stringify([ { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, @@ -58,13 +63,13 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+d', command: '-a' }, { key: 'cmd+c', command: 'b', args: { text: '`' } } ]); - const actual = mergeKeybindings(localContent, remoteContent, baseContent); + const actual = await testObject.merge(localContent, remoteContent, baseContent); assert.ok(!actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); }); - test('merge when local and remote are same with multiple entries in different order', () => { + test('merge when local and remote are same with multiple entries in different order', async () => { const localContent = stringify([ { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, @@ -75,13 +80,13 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, ]); - const actual = mergeKeybindings(localContent, remoteContent, null); + const actual = await testObject.merge(localContent, remoteContent, null); assert.ok(!actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); }); - test('merge when local and remote are same when remove entry is in different order', () => { + test('merge when local and remote are same when remove entry is in different order', async () => { const localContent = stringify([ { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, @@ -92,13 +97,13 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'cmd+c', command: 'b', args: { text: '`' } }, ]); - const actual = mergeKeybindings(localContent, remoteContent, null); + const actual = await testObject.merge(localContent, remoteContent, null); assert.ok(!actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); }); - test('merge when a new entry is added to remote', () => { + test('merge when a new entry is added to remote', async () => { const localContent = stringify([ { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, @@ -108,13 +113,13 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+d', command: '-a' }, { key: 'cmd+c', command: 'b', args: { text: '`' } }, ]); - const actual = mergeKeybindings(localContent, remoteContent, null); + const actual = await testObject.merge(localContent, remoteContent, null); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, remoteContent); }); - test('merge when multiple new entries are added to remote', () => { + test('merge when multiple new entries are added to remote', async () => { const localContent = stringify([ { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, @@ -125,13 +130,13 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'cmd+c', command: 'b', args: { text: '`' } }, { key: 'cmd+d', command: 'c' }, ]); - const actual = mergeKeybindings(localContent, remoteContent, null); + const actual = await testObject.merge(localContent, remoteContent, null); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, remoteContent); }); - test('merge when multiple new entries are added to remote from base and local has not changed', () => { + test('merge when multiple new entries are added to remote from base and local has not changed', async () => { const localContent = stringify([ { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, @@ -142,13 +147,13 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'cmd+c', command: 'b', args: { text: '`' } }, { key: 'cmd+d', command: 'c' }, ]); - const actual = mergeKeybindings(localContent, remoteContent, localContent); + const actual = await testObject.merge(localContent, remoteContent, localContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, remoteContent); }); - test('merge when an entry is removed from remote from base and local has not changed', () => { + test('merge when an entry is removed from remote from base and local has not changed', async () => { const localContent = stringify([ { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, @@ -158,13 +163,13 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, ]); - const actual = mergeKeybindings(localContent, remoteContent, localContent); + const actual = await testObject.merge(localContent, remoteContent, localContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, remoteContent); }); - test('merge when an entry (same command) is removed from remote from base and local has not changed', () => { + test('merge when an entry (same command) is removed from remote from base and local has not changed', async () => { const localContent = stringify([ { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, @@ -172,26 +177,26 @@ suite('KeybindingsMerge - No Conflicts', () => { const remoteContent = stringify([ { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, ]); - const actual = mergeKeybindings(localContent, remoteContent, localContent); + const actual = await testObject.merge(localContent, remoteContent, localContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, remoteContent); }); - test('merge when an entry is updated in remote from base and local has not changed', () => { + test('merge when an entry is updated in remote from base and local has not changed', async () => { const localContent = stringify([ { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, ]); const remoteContent = stringify([ { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, ]); - const actual = mergeKeybindings(localContent, remoteContent, localContent); + const actual = await testObject.merge(localContent, remoteContent, localContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, remoteContent); }); - test('merge when a command with multiple entries is updated from remote from base and local has not changed', () => { + test('merge when a command with multiple entries is updated from remote from base and local has not changed', async () => { const localContent = stringify([ { key: 'shift+c', command: 'c' }, { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, @@ -210,13 +215,13 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'cmd+d', command: 'a' }, { key: 'alt+d', command: 'b' }, ]); - const actual = mergeKeybindings(localContent, remoteContent, localContent); + const actual = await testObject.merge(localContent, remoteContent, localContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, expected); }); - test('merge when remote has moved forwareded with multiple changes and local stays with base', () => { + test('merge when remote has moved forwareded with multiple changes and local stays with base', async () => { const localContent = stringify([ { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'cmd+c', command: 'b', args: { text: '`' } }, @@ -242,13 +247,13 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+f', command: 'f' }, { key: 'alt+d', command: '-f' }, ]); - const actual = mergeKeybindings(localContent, remoteContent, localContent); + const actual = await testObject.merge(localContent, remoteContent, localContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, expected); }); - test('merge when a new entry is added to local', () => { + test('merge when a new entry is added to local', async () => { const localContent = stringify([ { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, @@ -258,13 +263,13 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, ]); - const actual = mergeKeybindings(localContent, remoteContent, null); + const actual = await testObject.merge(localContent, remoteContent, null); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); }); - test('merge when multiple new entries are added to local', () => { + test('merge when multiple new entries are added to local', async () => { const localContent = stringify([ { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, @@ -275,13 +280,13 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, ]); - const actual = mergeKeybindings(localContent, remoteContent, null); + const actual = await testObject.merge(localContent, remoteContent, null); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); }); - test('merge when multiple new entries are added to local from base and remote is not changed', () => { + test('merge when multiple new entries are added to local from base and remote is not changed', async () => { const localContent = stringify([ { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, @@ -292,13 +297,13 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, ]); - const actual = mergeKeybindings(localContent, remoteContent, remoteContent); + const actual = await testObject.merge(localContent, remoteContent, remoteContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); }); - test('merge when an entry is removed from local from base and remote has not changed', () => { + test('merge when an entry is removed from local from base and remote has not changed', async () => { const localContent = stringify([ { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, @@ -308,13 +313,13 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+d', command: '-a' }, { key: 'cmd+c', command: 'b', args: { text: '`' } }, ]); - const actual = mergeKeybindings(localContent, remoteContent, remoteContent); + const actual = await testObject.merge(localContent, remoteContent, remoteContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); }); - test('merge when an entry (with same command) is removed from local from base and remote has not changed', () => { + test('merge when an entry (with same command) is removed from local from base and remote has not changed', async () => { const localContent = stringify([ { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, ]); @@ -322,26 +327,26 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, ]); - const actual = mergeKeybindings(localContent, remoteContent, remoteContent); + const actual = await testObject.merge(localContent, remoteContent, remoteContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); }); - test('merge when an entry is updated in local from base and remote has not changed', () => { + test('merge when an entry is updated in local from base and remote has not changed', async () => { const localContent = stringify([ { key: 'alt+d', command: 'a', when: 'editorTextFocus' }, ]); const remoteContent = stringify([ { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, ]); - const actual = mergeKeybindings(localContent, remoteContent, remoteContent); + const actual = await testObject.merge(localContent, remoteContent, remoteContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); }); - test('merge when a command with multiple entries is updated from local from base and remote has not changed', () => { + test('merge when a command with multiple entries is updated from local from base and remote has not changed', async () => { const localContent = stringify([ { key: 'shift+c', command: 'c' }, { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, @@ -354,13 +359,13 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+d', command: 'b' }, { key: 'cmd+d', command: 'a' }, ]); - const actual = mergeKeybindings(localContent, remoteContent, remoteContent); + const actual = await testObject.merge(localContent, remoteContent, remoteContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); }); - test('merge when local has moved forwareded with multiple changes and remote stays with base', () => { + test('merge when local has moved forwareded with multiple changes and remote stays with base', async () => { const localContent = stringify([ { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'cmd+e', command: 'd' }, @@ -386,13 +391,13 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'cmd+d', command: 'c', when: 'context1' }, { key: 'cmd+c', command: '-c' }, ]); - const actual = mergeKeybindings(localContent, remoteContent, remoteContent); + const actual = await testObject.merge(localContent, remoteContent, remoteContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, expected); }); - test('merge when local and remote has moved forwareded with no conflicts', () => { + test('merge when local and remote has moved forwareded with no conflicts', async () => { const baseContent = stringify([ { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+c', command: '-a' }, @@ -427,7 +432,7 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+e', command: 'e' }, { key: 'alt+g', command: 'g', when: 'context2' }, ]); - const actual = mergeKeybindings(localContent, remoteContent, baseContent); + const actual = await testObject.merge(localContent, remoteContent, baseContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, expected); @@ -437,10 +442,10 @@ suite('KeybindingsMerge - No Conflicts', () => { suite('KeybindingsMerge - Conflicts', () => { - test('merge when local and remote with one entry but different value', () => { + test('merge when local and remote with one entry but different value', async () => { const localContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const actual = mergeKeybindings(localContent, remoteContent, null); + const actual = await testObject.merge(localContent, remoteContent, null); assert.ok(actual.hasChanges); assert.ok(actual.hasConflicts); assert.equal(actual.mergeContent, @@ -461,7 +466,7 @@ suite('KeybindingsMerge - Conflicts', () => { ]`); }); - test('merge when local and remote with different keybinding', () => { + test('merge when local and remote with different keybinding', async () => { const localContent = stringify([ { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+a', command: '-a', when: 'editorTextFocus && !editorReadonly' } @@ -470,7 +475,7 @@ suite('KeybindingsMerge - Conflicts', () => { { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+a', command: '-a', when: 'editorTextFocus && !editorReadonly' } ]); - const actual = mergeKeybindings(localContent, remoteContent, null); + const actual = await testObject.merge(localContent, remoteContent, null); assert.ok(actual.hasChanges); assert.ok(actual.hasConflicts); assert.equal(actual.mergeContent, @@ -501,7 +506,7 @@ suite('KeybindingsMerge - Conflicts', () => { ]`); }); - test('merge when local and remote has entries in different order', () => { + test('merge when local and remote has entries in different order', async () => { const localContent = stringify([ { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+a', command: 'a', when: 'editorTextFocus' } @@ -510,7 +515,7 @@ suite('KeybindingsMerge - Conflicts', () => { { key: 'alt+a', command: 'a', when: 'editorTextFocus' }, { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' } ]); - const actual = mergeKeybindings(localContent, remoteContent, null); + const actual = await testObject.merge(localContent, remoteContent, null); assert.ok(actual.hasChanges); assert.ok(actual.hasConflicts); assert.equal(actual.mergeContent, @@ -541,11 +546,11 @@ suite('KeybindingsMerge - Conflicts', () => { ]`); }); - test('merge when the entry is removed in local but updated in remote', () => { + test('merge when the entry is removed in local but updated in remote', async () => { const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); const localContent = stringify([]); const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const actual = mergeKeybindings(localContent, remoteContent, baseContent); + const actual = await testObject.merge(localContent, remoteContent, baseContent); assert.ok(actual.hasChanges); assert.ok(actual.hasConflicts); assert.equal(actual.mergeContent, @@ -561,11 +566,11 @@ suite('KeybindingsMerge - Conflicts', () => { ]`); }); - test('merge when the entry is removed in local but updated in remote and a new entry is added in local', () => { + test('merge when the entry is removed in local but updated in remote and a new entry is added in local', async () => { const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); const localContent = stringify([{ key: 'alt+b', command: 'b' }]); const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const actual = mergeKeybindings(localContent, remoteContent, baseContent); + const actual = await testObject.merge(localContent, remoteContent, baseContent); assert.ok(actual.hasChanges); assert.ok(actual.hasConflicts); assert.equal(actual.mergeContent, @@ -585,11 +590,11 @@ suite('KeybindingsMerge - Conflicts', () => { ]`); }); - test('merge when the entry is removed in remote but updated in local', () => { + test('merge when the entry is removed in remote but updated in local', async () => { const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); const remoteContent = stringify([]); - const actual = mergeKeybindings(localContent, remoteContent, baseContent); + const actual = await testObject.merge(localContent, remoteContent, baseContent); assert.ok(actual.hasChanges); assert.ok(actual.hasConflicts); assert.equal(actual.mergeContent, @@ -605,11 +610,11 @@ suite('KeybindingsMerge - Conflicts', () => { ]`); }); - test('merge when the entry is removed in remote but updated in local and a new entry is added in remote', () => { + test('merge when the entry is removed in remote but updated in local and a new entry is added in remote', async () => { const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); const remoteContent = stringify([{ key: 'alt+b', command: 'b' }]); - const actual = mergeKeybindings(localContent, remoteContent, baseContent); + const actual = await testObject.merge(localContent, remoteContent, baseContent); assert.ok(actual.hasChanges); assert.ok(actual.hasConflicts); assert.equal(actual.mergeContent, diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 5e499d4aa41..ebc12c69d14 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -80,6 +80,7 @@ import 'vs/workbench/services/extensionManagement/common/extensionEnablementServ import 'vs/workbench/services/notification/common/notificationService'; import 'vs/workbench/services/extensions/common/staticExtensions'; import 'vs/workbench/services/userDataSync/common/settingsMergeService'; +import 'vs/workbench/services/keybinding/common/keybindingsMerge'; import 'vs/workbench/services/path/common/remotePathService'; import 'vs/workbench/services/remote/common/remoteExplorerService'; import 'vs/workbench/services/workingCopy/common/workingCopyService'; From 80ca1300e6e779a0fbf05dd85a26367c1d6e715f Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 28 Nov 2019 16:38:57 +0100 Subject: [PATCH 037/255] implemente better merging strategy --- .../keybinding/common/keybindingsMerge.ts | 338 ++++++++----- .../electron-browser/keybindingsMerge.test.ts | 443 ++++++++++-------- 2 files changed, 455 insertions(+), 326 deletions(-) diff --git a/src/vs/workbench/services/keybinding/common/keybindingsMerge.ts b/src/vs/workbench/services/keybinding/common/keybindingsMerge.ts index fda747c4861..4355ecca6ec 100644 --- a/src/vs/workbench/services/keybinding/common/keybindingsMerge.ts +++ b/src/vs/workbench/services/keybinding/common/keybindingsMerge.ts @@ -4,179 +4,248 @@ *--------------------------------------------------------------------------------------------*/ import * as objects from 'vs/base/common/objects'; -import { parse, findNodeAtLocation, parseTree } from 'vs/base/common/json'; +import { parse } from 'vs/base/common/json'; import { values, keys } from 'vs/base/common/map'; -import { IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding'; +import { IUserFriendlyKeybinding, IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { firstIndex as findFirstIndex, equals } from 'vs/base/common/arrays'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import * as contentUtil from 'vs/platform/userDataSync/common/content'; import { IKeybindingsMergeService } from 'vs/platform/userDataSync/common/userDataSync'; +interface ICompareResult { + added: Set; + removed: Set; + updated: Set; +} + +interface IMergeResult { + added: Set; + removed: Set; + updated: Set; + conflicts: Set; +} + export class KeybindingsMergeService implements IKeybindingsMergeService { _serviceBrand: undefined; + constructor( + @IKeybindingService private readonly keybindingsService: IKeybindingService + ) { } + public async merge(localContent: string, remoteContent: string, baseContent: string | null): Promise<{ mergeContent: string, hasChanges: boolean, hasConflicts: boolean }> { const local = parse(localContent); const remote = parse(remoteContent); const base = baseContent ? parse(baseContent) : null; - const byCommand = (keybindings: IUserFriendlyKeybinding[]) => { - const map: Map = new Map(); - for (const keybinding of keybindings) { - const command = keybinding.command[0] === '-' ? keybinding.command.substring(1) : keybinding.command; + const normalize = (keybinding: IUserFriendlyKeybinding): IUserFriendlyKeybinding => ({ + ...keybinding, + ...{ + key: this.keybindingsService.resolveUserBinding(keybinding.key).map(part => part.getUserSettingsLabel()).join(' ') + } + }); + + const normalizedLocal = local.map(keybinding => normalize(keybinding)); + const normalizedRemote = remote.map(keybinding => normalize(keybinding)); + const normalizedBase = base ? base.map(keybinding => normalize(keybinding)) : null; + + const byKeybinding = (keybindings: IUserFriendlyKeybinding[], normalized: IUserFriendlyKeybinding[]) => { + const map: Map = new Map(); + for (let index = 0; index < normalized.length; index++) { + let value = map.get(normalized[index].key); + if (!value) { + value = []; + map.set(normalized[index].key, value); + } + value.push({ keybinding: keybindings[index], normalized: normalized[index] }); + } + return map; + }; + + const localByKeybinding = byKeybinding(local, normalizedLocal); + const remoteByKeybinding = byKeybinding(remote, normalizedRemote); + const baseByKeybinding = base ? byKeybinding(base, normalizedBase!) : null; + + const localToRemoteByKeybinding = this.compareByKeybinding(localByKeybinding, remoteByKeybinding); + if (localToRemoteByKeybinding.added.size === 0 && localToRemoteByKeybinding.removed.size === 0 && localToRemoteByKeybinding.updated.size === 0) { + // No changes found between local and remote. + return { mergeContent: localContent, hasChanges: false, hasConflicts: false }; + } + + const baseToLocalByKeybinding = baseByKeybinding ? this.compareByKeybinding(baseByKeybinding, localByKeybinding) : { added: keys(localByKeybinding).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; + if (baseToLocalByKeybinding.added.size === 0 && baseToLocalByKeybinding.removed.size === 0 && baseToLocalByKeybinding.updated.size === 0) { + // Remote has moved forward and local has not. Return remote + return { mergeContent: remoteContent, hasChanges: true, hasConflicts: false }; + } + + const baseToRemoteByKeybinding = baseByKeybinding ? this.compareByKeybinding(baseByKeybinding, remoteByKeybinding) : { added: keys(remoteByKeybinding).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; + if (baseToRemoteByKeybinding.added.size === 0 && baseToRemoteByKeybinding.removed.size === 0 && baseToRemoteByKeybinding.updated.size === 0) { + // Local has moved forward and remote has not. Return local. + return { mergeContent: localContent, hasChanges: true, hasConflicts: false }; + } + + // Both local and remote has moved forward. + const byCommand = (keybindings: IUserFriendlyKeybinding[], normalized: IUserFriendlyKeybinding[]) => { + const map: Map = new Map(); + for (let index = 0; index < normalized.length; index++) { + const command = normalized[index].command[0] === '-' ? normalized[index].command.substring(1) : normalized[index].command; let value = map.get(command); if (!value) { value = []; map.set(command, value); } - value.push(keybinding); + value.push({ keybinding: keybindings[index], normalized: normalized[index] }); } return map; }; + const localByCommand = byCommand(local, normalizedLocal); + const remoteByCommand = byCommand(remote, normalizedRemote); + const baseByCommand = base ? byCommand(base, normalizedBase!) : null; + const localToRemoteByCommand = this.compareByCommand(localByCommand, remoteByCommand); + const baseToLocalByCommand = baseByCommand ? this.compareByCommand(baseByCommand, localByCommand) : { added: keys(localByCommand).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; + const baseToRemoteByCommand = baseByCommand ? this.compareByCommand(baseByCommand, remoteByCommand) : { added: keys(remoteByCommand).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; - const localByCommand = byCommand(local); - const remoteByCommand = byCommand(remote); - const baseByCommand = base ? byCommand(base) : null; - - const localToRemote = this.compare(localByCommand, remoteByCommand); - if (localToRemote.added.size === 0 && localToRemote.removed.size === 0 && localToRemote.updated.size === 0) { - // No changes found between local and remote. - return { mergeContent: localContent, hasChanges: false, hasConflicts: false }; - } - - const conflictCommands: Set = new Set(); - const baseToLocal = baseByCommand ? this.compare(baseByCommand, localByCommand) : { added: keys(localByCommand).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; - const baseToRemote = baseByCommand ? this.compare(baseByCommand, remoteByCommand) : { added: keys(remoteByCommand).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; + const commandsMergeResult = this.computeMergeResult(localToRemoteByCommand, baseToLocalByCommand, baseToRemoteByCommand); + const keybindingsMergeResult = this.computeMergeResult(localToRemoteByKeybinding, baseToLocalByKeybinding, baseToRemoteByKeybinding); const eol = contentUtil.getEol(localContent); let mergeContent = localContent; - // Removed commands in Local - for (const command of values(baseToLocal.removed)) { - // Got updated in remote - if (baseToRemote.updated.has(command)) { - conflictCommands.add(command); - } - } - // Removed commands in Remote - for (const command of values(baseToRemote.removed)) { - if (conflictCommands.has(command)) { + for (const command of values(commandsMergeResult.removed)) { + if (commandsMergeResult.conflicts.has(command)) { continue; } - // Got updated in local - if (baseToLocal.updated.has(command)) { - conflictCommands.add(command); - } else { - // remove the command - mergeContent = this.removeKeybindings(mergeContent, eol, command); - } - } - - // Added commands in Local - for (const command of values(baseToLocal.added)) { - if (conflictCommands.has(command)) { - continue; - } - // Got added in remote - if (baseToRemote.added.has(command)) { - // Has different value - if (localToRemote.updated.has(command)) { - conflictCommands.add(command); - } - } + mergeContent = this.removeKeybindings(mergeContent, eol, command); } // Added commands in remote - for (const command of values(baseToRemote.added)) { - if (conflictCommands.has(command)) { + for (const command of values(commandsMergeResult.added)) { + if (commandsMergeResult.conflicts.has(command)) { continue; } - // Got added in local - if (baseToLocal.added.has(command)) { - // Has different value - if (localToRemote.updated.has(command)) { - conflictCommands.add(command); - } - } else { - mergeContent = this.addKeybinding(mergeContent, eol, command, remoteByCommand.get(command)!); - } - } - - // Updated commands in Local - for (const command of values(baseToLocal.updated)) { - if (conflictCommands.has(command)) { + const keybindings = remoteByCommand.get(command)!; + if (keybindings.some(({ normalized }) => keybindingsMergeResult.conflicts.has(normalized.key))) { continue; } - // Got updated in remote - if (baseToRemote.updated.has(command)) { - // Has different value - if (localToRemote.updated.has(command)) { - conflictCommands.add(command); - } - } + mergeContent = this.addKeybindings(mergeContent, eol, keybindings.map(({ keybinding }) => keybinding)); } // Updated commands in Remote - for (const command of values(baseToRemote.updated)) { - if (conflictCommands.has(command)) { + for (const command of values(commandsMergeResult.updated)) { + if (commandsMergeResult.conflicts.has(command)) { + continue; + } + const keybindings = remoteByCommand.get(command)!; + if (keybindings.some(({ normalized }) => keybindingsMergeResult.conflicts.has(normalized.key))) { + continue; + } + mergeContent = this.updateKeybindings(mergeContent, eol, command, keybindings.map(({ keybinding }) => keybinding)); + } + + const hasConflicts = commandsMergeResult.conflicts.size > 0 || keybindingsMergeResult.conflicts.size > 0; + if (hasConflicts) { + mergeContent = `<<<<<<< local${eol}` + + mergeContent + + `${eol}=======${eol}` + + remoteContent + + `${eol}>>>>>>> remote`; + } + + return { mergeContent, hasChanges: true, hasConflicts }; + } + + private computeMergeResult(localToRemote: ICompareResult, baseToLocal: ICompareResult, baseToRemote: ICompareResult): IMergeResult { + const added: Set = new Set(); + const removed: Set = new Set(); + const updated: Set = new Set(); + const conflicts: Set = new Set(); + + // Removed keys in Local + for (const key of values(baseToLocal.removed)) { + // Got updated in remote + if (baseToRemote.updated.has(key)) { + conflicts.add(key); + } + } + + // Removed keys in Remote + for (const key of values(baseToRemote.removed)) { + if (conflicts.has(key)) { continue; } // Got updated in local - if (baseToLocal.updated.has(command)) { + if (baseToLocal.updated.has(key)) { + conflicts.add(key); + } else { + // remove the key + removed.add(key); + } + } + + // Added keys in Local + for (const key of values(baseToLocal.added)) { + if (conflicts.has(key)) { + continue; + } + // Got added in remote + if (baseToRemote.added.has(key)) { // Has different value - if (localToRemote.updated.has(command)) { - conflictCommands.add(command); + if (localToRemote.updated.has(key)) { + conflicts.add(key); + } + } + } + + // Added keys in remote + for (const key of values(baseToRemote.added)) { + if (conflicts.has(key)) { + continue; + } + // Got added in local + if (baseToLocal.added.has(key)) { + // Has different value + if (localToRemote.updated.has(key)) { + conflicts.add(key); } } else { - // update the command - mergeContent = this.updateKeybinding(mergeContent, eol, command, remoteByCommand.get(command)!); + added.add(key); } } - const conflicts: { command: string, local: IUserFriendlyKeybinding[] | undefined, remote: IUserFriendlyKeybinding[] | undefined, firstIndex: number }[] = []; - - for (const command of values(conflictCommands)) { - const local = localByCommand.get(command); - const remote = remoteByCommand.get(command); - mergeContent = this.updateKeybinding(mergeContent, eol, command, [...local || [], ...remote || []]); - conflicts.push({ command, local, remote, firstIndex: -1 }); - } - - const allKeybindings = parse(mergeContent); - for (const conflict of conflicts) { - conflict.firstIndex = findFirstIndex(allKeybindings, keybinding => keybinding.command === conflict.command || keybinding.command === `-${conflict.command}`); - } - // Sort reverse so that conflicts content is added from last - conflicts.sort((a, b) => b.firstIndex - a.firstIndex); - - const tree = parseTree(mergeContent); - for (const { firstIndex, local, remote } of conflicts) { - const firstNode = findNodeAtLocation(tree, [firstIndex])!; - const startLocalOffset = contentUtil.getLineStartOffset(mergeContent, eol, firstNode.offset) - eol.length; - let endLocalOffset = startLocalOffset; - if (local) { - const lastLocalValueNode = findNodeAtLocation(tree, [firstIndex + local.length - 1])!; - endLocalOffset = contentUtil.getLineEndOffset(mergeContent, eol, lastLocalValueNode.offset + lastLocalValueNode.length); + // Updated keys in Local + for (const key of values(baseToLocal.updated)) { + if (conflicts.has(key)) { + continue; } - let remoteOffset = endLocalOffset; - if (remote) { - const lastRemoteValueNode = findNodeAtLocation(tree, [firstIndex + (local ? local.length : 0) + remote.length - 1])!; - remoteOffset = contentUtil.getLineEndOffset(mergeContent, eol, lastRemoteValueNode.offset + lastRemoteValueNode.length); + // Got updated in remote + if (baseToRemote.updated.has(key)) { + // Has different value + if (localToRemote.updated.has(key)) { + conflicts.add(key); + } } - mergeContent = mergeContent.substring(0, startLocalOffset) - + `${eol}<<<<<<< local` - + mergeContent.substring(startLocalOffset, endLocalOffset) - + `${eol}=======` - + mergeContent.substring(endLocalOffset, remoteOffset) - + `${eol}>>>>>>> remote` - + mergeContent.substring(remoteOffset); } - return { mergeContent, hasChanges: true, hasConflicts: conflicts.length > 0 }; + // Updated keys in Remote + for (const key of values(baseToRemote.updated)) { + if (conflicts.has(key)) { + continue; + } + // Got updated in local + if (baseToLocal.updated.has(key)) { + // Has different value + if (localToRemote.updated.has(key)) { + conflicts.add(key); + } + } else { + // updated key + updated.add(key); + } + } + return { added, removed, updated, conflicts }; } - private compare(from: Map, to: Map): { added: Set, removed: Set, updated: Set } { + private compareByKeybinding(from: Map, to: Map): ICompareResult { const fromKeys = keys(from); const toKeys = keys(to); const added = toKeys.filter(key => fromKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); @@ -187,9 +256,9 @@ export class KeybindingsMergeService implements IKeybindingsMergeService { if (removed.has(key)) { continue; } - const value1: IUserFriendlyKeybinding[] = from.get(key)!; - const value2: IUserFriendlyKeybinding[] = to.get(key)!; - if (!this.areSameKeybindings(value1, value2)) { + const value1: IUserFriendlyKeybinding[] = from.get(key)!.map(({ normalized }) => normalized); + const value2: IUserFriendlyKeybinding[] = to.get(key)!.map(({ normalized }) => normalized); + if (!equals(value1, value2, (a, b) => this.isSameKeybinding(a, b))) { updated.add(key); } } @@ -197,7 +266,28 @@ export class KeybindingsMergeService implements IKeybindingsMergeService { return { added, removed, updated }; } - private areSameKeybindings(value1: IUserFriendlyKeybinding[], value2: IUserFriendlyKeybinding[]): boolean { + private compareByCommand(from: Map, to: Map): ICompareResult { + const fromKeys = keys(from); + const toKeys = keys(to); + const added = toKeys.filter(key => fromKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); + const removed = fromKeys.filter(key => toKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); + const updated: Set = new Set(); + + for (const key of fromKeys) { + if (removed.has(key)) { + continue; + } + const value1: IUserFriendlyKeybinding[] = from.get(key)!.map(({ normalized }) => normalized); + const value2: IUserFriendlyKeybinding[] = to.get(key)!.map(({ normalized }) => normalized); + if (!this.areSameKeybindingsWithSameCommand(value1, value2)) { + updated.add(key); + } + } + + return { added, removed, updated }; + } + + private areSameKeybindingsWithSameCommand(value1: IUserFriendlyKeybinding[], value2: IUserFriendlyKeybinding[]): boolean { // Compare entries adding keybindings if (!equals(value1.filter(({ command }) => command[0] !== '-'), value2.filter(({ command }) => command[0] !== '-'), (a, b) => this.isSameKeybinding(a, b))) { return false; @@ -230,7 +320,7 @@ export class KeybindingsMergeService implements IKeybindingsMergeService { return true; } - private addKeybinding(content: string, eol: string, command: string, keybindings: IUserFriendlyKeybinding[]): string { + private addKeybindings(content: string, eol: string, keybindings: IUserFriendlyKeybinding[]): string { for (const keybinding of keybindings) { content = contentUtil.edit(content, eol, [-1], keybinding); } @@ -247,7 +337,7 @@ export class KeybindingsMergeService implements IKeybindingsMergeService { return content; } - private updateKeybinding(content: string, eol: string, command: string, keybindings: IUserFriendlyKeybinding[]): string { + private updateKeybindings(content: string, eol: string, command: string, keybindings: IUserFriendlyKeybinding[]): string { const allKeybindings = parse(content); const location = findFirstIndex(allKeybindings, keybinding => keybinding.command === command || keybinding.command === `-${command}`); // Remove all entries with this command diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingsMerge.test.ts b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingsMerge.test.ts index 3df3c0293f6..f858760cbd5 100644 --- a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingsMerge.test.ts +++ b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingsMerge.test.ts @@ -4,12 +4,23 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { workbenchInstantiationService } from 'vs/workbench/test/workbenchTestServices'; +import { OS } from 'vs/base/common/platform'; import { KeybindingsMergeService } from 'vs/workbench/services/keybinding/common/keybindingsMerge'; +import { MockKeybindingService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; +import { ResolvedKeybinding, ChordKeybinding, SimpleKeybinding } from 'vs/base/common/keyCodes'; +import { KeybindingParser } from 'vs/base/common/keybindingParser'; +import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; let testObject: KeybindingsMergeService; -suiteSetup(() => testObject = workbenchInstantiationService().createInstance(KeybindingsMergeService)); +suiteSetup(() => { + testObject = new KeybindingsMergeService(new class extends MockKeybindingService { + resolveUserBinding(userBinding: string): ResolvedKeybinding[] { + const parts = KeybindingParser.parseUserBinding(userBinding); + return [new USLayoutResolvedKeybinding(new ChordKeybinding(parts), OS)]; + } + }); +}); suite('KeybindingsMerge - No Conflicts', () => { @@ -31,6 +42,21 @@ suite('KeybindingsMerge - No Conflicts', () => { assert.equal(actual.mergeContent, localContent); }); + test('merge when local and remote has entries in different order', async () => { + const localContent = stringify([ + { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+a', command: 'a', when: 'editorTextFocus' } + ]); + const remoteContent = stringify([ + { key: 'alt+a', command: 'a', when: 'editorTextFocus' }, + { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' } + ]); + const actual = await testObject.merge(localContent, remoteContent, null); + assert.ok(!actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, localContent); + }); + test('merge when local and remote are same with multiple entries', async () => { const localContent = stringify([ { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, @@ -209,16 +235,10 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+d', command: 'b' }, { key: 'cmd+d', command: 'a' }, ]); - const expected = stringify([ - { key: 'shift+c', command: 'c' }, - { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, - { key: 'cmd+d', command: 'a' }, - { key: 'alt+d', command: 'b' }, - ]); const actual = await testObject.merge(localContent, remoteContent, localContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); - assert.equal(actual.mergeContent, expected); + assert.equal(actual.mergeContent, remoteContent); }); test('merge when remote has moved forwareded with multiple changes and local stays with base', async () => { @@ -238,19 +258,10 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'cmd+d', command: 'c', when: 'context1' }, { key: 'cmd+c', command: '-c' }, ]); - const expected = stringify([ - { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, - { key: 'alt+d', command: '-a' }, - { key: 'cmd+e', command: 'd' }, - { key: 'cmd+d', command: 'c', when: 'context1' }, - { key: 'cmd+c', command: '-c' }, - { key: 'alt+f', command: 'f' }, - { key: 'alt+d', command: '-f' }, - ]); const actual = await testObject.merge(localContent, remoteContent, localContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); - assert.equal(actual.mergeContent, expected); + assert.equal(actual.mergeContent, remoteContent); }); test('merge when a new entry is added to local', async () => { @@ -397,7 +408,177 @@ suite('KeybindingsMerge - No Conflicts', () => { assert.equal(actual.mergeContent, expected); }); - test('merge when local and remote has moved forwareded with no conflicts', async () => { +}); + +suite('KeybindingsMerge - Conflicts', () => { + + test('merge when local and remote with one entry but different value', async () => { + const localContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const actual = await testObject.merge(localContent, remoteContent, null); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local +[ + { + "key": "alt+d", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + } +] +======= +[ + { + "key": "alt+c", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + } +] +>>>>>>> remote`); + }); + + test('merge when local and remote with different keybinding', async () => { + const localContent = stringify([ + { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+a', command: '-a', when: 'editorTextFocus && !editorReadonly' } + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+a', command: '-a', when: 'editorTextFocus && !editorReadonly' } + ]); + const actual = await testObject.merge(localContent, remoteContent, null); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local +[ + { + "key": "alt+d", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + }, + { + "key": "alt+a", + "command": "-a", + "when": "editorTextFocus && !editorReadonly" + } +] +======= +[ + { + "key": "alt+c", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + }, + { + "key": "alt+a", + "command": "-a", + "when": "editorTextFocus && !editorReadonly" + } +] +>>>>>>> remote`); + }); + + test('merge when the entry is removed in local but updated in remote', async () => { + const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const localContent = stringify([]); + const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const actual = await testObject.merge(localContent, remoteContent, baseContent); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local +[] +======= +[ + { + "key": "alt+c", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + } +] +>>>>>>> remote`); + }); + + test('merge when the entry is removed in local but updated in remote and a new entry is added in local', async () => { + const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const localContent = stringify([{ key: 'alt+b', command: 'b' }]); + const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const actual = await testObject.merge(localContent, remoteContent, baseContent); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local +[ + { + "key": "alt+b", + "command": "b" + } +] +======= +[ + { + "key": "alt+c", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + } +] +>>>>>>> remote`); + }); + + test('merge when the entry is removed in remote but updated in local', async () => { + const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const remoteContent = stringify([]); + const actual = await testObject.merge(localContent, remoteContent, baseContent); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local +[ + { + "key": "alt+c", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + } +] +======= +[] +>>>>>>> remote`); + }); + + test('merge when the entry is removed in remote but updated in local and a new entry is added in remote', async () => { + const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const remoteContent = stringify([{ key: 'alt+b', command: 'b' }]); + const actual = await testObject.merge(localContent, remoteContent, baseContent); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local +[ + { + "key": "alt+c", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + }, + { + "key": "alt+b", + "command": "b" + } +] +======= +[ + { + "key": "alt+b", + "command": "b" + } +] +>>>>>>> remote`); + }); + + test('merge when local and remote has moved forwareded with conflicts', async () => { const baseContent = stringify([ { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+c', command: '-a' }, @@ -423,215 +604,73 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+c', command: 'c', when: 'context1' }, { key: 'alt+g', command: 'g', when: 'context2' }, ]); - const expected = stringify([ - { key: 'alt+d', command: '-f' }, - { key: 'cmd+d', command: 'd' }, - { key: 'cmd+c', command: '-c' }, - { key: 'alt+c', command: 'c', when: 'context1' }, - { key: 'alt+a', command: 'f' }, - { key: 'alt+e', command: 'e' }, - { key: 'alt+g', command: 'g', when: 'context2' }, - ]); const actual = await testObject.merge(localContent, remoteContent, baseContent); assert.ok(actual.hasChanges); - assert.ok(!actual.hasConflicts); - assert.equal(actual.mergeContent, expected); - }); - -}); - -suite('KeybindingsMerge - Conflicts', () => { - - test('merge when local and remote with one entry but different value', async () => { - const localContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const actual = await testObject.merge(localContent, remoteContent, null); - assert.ok(actual.hasChanges); assert.ok(actual.hasConflicts); assert.equal(actual.mergeContent, - `[ -<<<<<<< local + `<<<<<<< local +[ { "key": "alt+d", - "command": "a", - "when": "editorTextFocus && !editorReadonly" + "command": "-f" }, -======= { - "key": "alt+c", - "command": "a", - "when": "editorTextFocus && !editorReadonly" - } ->>>>>>> remote -]`); - }); - - test('merge when local and remote with different keybinding', async () => { - const localContent = stringify([ - { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, - { key: 'alt+a', command: '-a', when: 'editorTextFocus && !editorReadonly' } - ]); - const remoteContent = stringify([ - { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, - { key: 'alt+a', command: '-a', when: 'editorTextFocus && !editorReadonly' } - ]); - const actual = await testObject.merge(localContent, remoteContent, null); - assert.ok(actual.hasChanges); - assert.ok(actual.hasConflicts); - assert.equal(actual.mergeContent, - `[ -<<<<<<< local + "key": "cmd+d", + "command": "d" + }, { - "key": "alt+d", - "command": "a", - "when": "editorTextFocus && !editorReadonly" + "key": "cmd+c", + "command": "-c" + }, + { + "key": "cmd+d", + "command": "c", + "when": "context1" }, { "key": "alt+a", - "command": "-a", - "when": "editorTextFocus && !editorReadonly" - }, -======= - { - "key": "alt+c", - "command": "a", - "when": "editorTextFocus && !editorReadonly" + "command": "f" }, { - "key": "alt+a", - "command": "-a", - "when": "editorTextFocus && !editorReadonly" + "key": "alt+e", + "command": "e" + }, + { + "key": "alt+g", + "command": "g", + "when": "context2" } ->>>>>>> remote -]`); - }); - - test('merge when local and remote has entries in different order', async () => { - const localContent = stringify([ - { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, - { key: 'alt+a', command: 'a', when: 'editorTextFocus' } - ]); - const remoteContent = stringify([ - { key: 'alt+a', command: 'a', when: 'editorTextFocus' }, - { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' } - ]); - const actual = await testObject.merge(localContent, remoteContent, null); - assert.ok(actual.hasChanges); - assert.ok(actual.hasConflicts); - assert.equal(actual.mergeContent, - `[ -<<<<<<< local - { - "key": "alt+d", - "command": "a", - "when": "editorTextFocus && !editorReadonly" - }, - { - "key": "alt+a", - "command": "a", - "when": "editorTextFocus" - }, +] ======= +[ { "key": "alt+a", - "command": "a", - "when": "editorTextFocus" + "command": "f" + }, + { + "key": "cmd+c", + "command": "-c" + }, + { + "key": "cmd+d", + "command": "d" }, { "key": "alt+d", - "command": "a", - "when": "editorTextFocus && !editorReadonly" - } ->>>>>>> remote -]`); - }); - - test('merge when the entry is removed in local but updated in remote', async () => { - const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const localContent = stringify([]); - const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const actual = await testObject.merge(localContent, remoteContent, baseContent); - assert.ok(actual.hasChanges); - assert.ok(actual.hasConflicts); - assert.equal(actual.mergeContent, - `[ -<<<<<<< local -======= - { - "key": "alt+c", - "command": "a", - "when": "editorTextFocus && !editorReadonly" - } ->>>>>>> remote -]`); - }); - - test('merge when the entry is removed in local but updated in remote and a new entry is added in local', async () => { - const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const localContent = stringify([{ key: 'alt+b', command: 'b' }]); - const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const actual = await testObject.merge(localContent, remoteContent, baseContent); - assert.ok(actual.hasChanges); - assert.ok(actual.hasConflicts); - assert.equal(actual.mergeContent, - `[ - { - "key": "alt+b", - "command": "b" + "command": "-f" }, -<<<<<<< local -======= { "key": "alt+c", - "command": "a", - "when": "editorTextFocus && !editorReadonly" - } ->>>>>>> remote -]`); - }); - - test('merge when the entry is removed in remote but updated in local', async () => { - const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const remoteContent = stringify([]); - const actual = await testObject.merge(localContent, remoteContent, baseContent); - assert.ok(actual.hasChanges); - assert.ok(actual.hasConflicts); - assert.equal(actual.mergeContent, - `[ -<<<<<<< local - { - "key": "alt+c", - "command": "a", - "when": "editorTextFocus && !editorReadonly" - } -======= ->>>>>>> remote -]`); - }); - - test('merge when the entry is removed in remote but updated in local and a new entry is added in remote', async () => { - const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const remoteContent = stringify([{ key: 'alt+b', command: 'b' }]); - const actual = await testObject.merge(localContent, remoteContent, baseContent); - assert.ok(actual.hasChanges); - assert.ok(actual.hasConflicts); - assert.equal(actual.mergeContent, - `[ -<<<<<<< local - { - "key": "alt+c", - "command": "a", - "when": "editorTextFocus && !editorReadonly" + "command": "c", + "when": "context1" }, -======= ->>>>>>> remote { - "key": "alt+b", - "command": "b" + "key": "alt+g", + "command": "g", + "when": "context2" } -]`); +] +>>>>>>> remote`); }); }); From 76801c7c52db690f5ada6cbf98ce0368709682a5 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 28 Nov 2019 16:58:31 +0100 Subject: [PATCH 038/255] improve merge by ignoring negared commands --- .../keybinding/common/keybindingsMerge.ts | 10 +++-- .../electron-browser/keybindingsMerge.test.ts | 43 +++++++++++++++++++ 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/services/keybinding/common/keybindingsMerge.ts b/src/vs/workbench/services/keybinding/common/keybindingsMerge.ts index 4355ecca6ec..47f3cca8f65 100644 --- a/src/vs/workbench/services/keybinding/common/keybindingsMerge.ts +++ b/src/vs/workbench/services/keybinding/common/keybindingsMerge.ts @@ -124,7 +124,9 @@ export class KeybindingsMergeService implements IKeybindingsMergeService { continue; } const keybindings = remoteByCommand.get(command)!; - if (keybindings.some(({ normalized }) => keybindingsMergeResult.conflicts.has(normalized.key))) { + // Ignore negated commands + if (keybindings.some(({ normalized }) => normalized.command !== `-${command}` && keybindingsMergeResult.conflicts.has(normalized.key))) { + commandsMergeResult.conflicts.add(command); continue; } mergeContent = this.addKeybindings(mergeContent, eol, keybindings.map(({ keybinding }) => keybinding)); @@ -136,13 +138,15 @@ export class KeybindingsMergeService implements IKeybindingsMergeService { continue; } const keybindings = remoteByCommand.get(command)!; - if (keybindings.some(({ normalized }) => keybindingsMergeResult.conflicts.has(normalized.key))) { + // Ignore negated commands + if (keybindings.some(({ normalized }) => normalized.command !== `-${command}` && keybindingsMergeResult.conflicts.has(normalized.key))) { + commandsMergeResult.conflicts.add(command); continue; } mergeContent = this.updateKeybindings(mergeContent, eol, command, keybindings.map(({ keybinding }) => keybinding)); } - const hasConflicts = commandsMergeResult.conflicts.size > 0 || keybindingsMergeResult.conflicts.size > 0; + const hasConflicts = commandsMergeResult.conflicts.size > 0; if (hasConflicts) { mergeContent = `<<<<<<< local${eol}` + mergeContent diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingsMerge.test.ts b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingsMerge.test.ts index f858760cbd5..9529d3150a8 100644 --- a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingsMerge.test.ts +++ b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingsMerge.test.ts @@ -408,6 +408,49 @@ suite('KeybindingsMerge - No Conflicts', () => { assert.equal(actual.mergeContent, expected); }); + test('merge when local and remote has moved forwareded with conflicts', async () => { + const baseContent = stringify([ + { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'ctrl+c', command: '-a' }, + { key: 'cmd+e', command: 'd' }, + { key: 'alt+a', command: 'f' }, + { key: 'alt+d', command: '-f' }, + { key: 'cmd+d', command: 'c', when: 'context1' }, + { key: 'cmd+c', command: '-c' }, + ]); + const localContent = stringify([ + { key: 'alt+d', command: '-f' }, + { key: 'cmd+e', command: 'd' }, + { key: 'cmd+c', command: '-c' }, + { key: 'cmd+d', command: 'c', when: 'context1' }, + { key: 'alt+a', command: 'f' }, + { key: 'alt+e', command: 'e' }, + ]); + const remoteContent = stringify([ + { key: 'alt+a', command: 'f' }, + { key: 'cmd+c', command: '-c' }, + { key: 'cmd+d', command: 'd' }, + { key: 'alt+d', command: '-f' }, + { key: 'alt+c', command: 'c', when: 'context1' }, + { key: 'alt+g', command: 'g', when: 'context2' }, + ]); + const expected = stringify([ + { key: 'alt+d', command: '-f' }, + { key: 'cmd+d', command: 'd' }, + { key: 'cmd+c', command: '-c' }, + { key: 'alt+c', command: 'c', when: 'context1' }, + { key: 'alt+a', command: 'f' }, + { key: 'alt+e', command: 'e' }, + { key: 'alt+g', command: 'g', when: 'context2' }, + ]); + //'[\n\t{\n\t\t"key": "alt+d",\n\t\t"command": "-f"\n\t},\n\t{\n\t\t"key": "cmd+d",\n\t\t"command": "d"\n\t},\n\t{\n\t\t"key": "cmd+c",\n\t\t"command": "-c"\n\t},\n\t{\n\t\t"key": "cmd+d",\n\t\t"command": "c",\n\t\t"when": "context1"\n\t},\n\t{\n\t\t"key": "alt+a",\n\t\t"command": "f"\n\t},\n\t{\n\t\t"key": "alt+e",\n\t\t"command": "e"\n\t},\n\t{\n\t\t"key": "alt+g",\n\t\t"command": "g",\n\t\t"when": "context2"\n\t}\n]' + //'[\n\t{\n\t\t"key": "alt+d",\n\t\t"command": "-f"\n\t},\n\t{\n\t\t"key": "cmd+d",\n\t\t"command": "d"\n\t},\n\t{\n\t\t"key": "cmd+c",\n\t\t"command": "-c"\n\t},\n\t{\n\t\t"key": "alt+c",\n\t\t"command": "c",\n\t\t"when": "context1"\n\t},\n\t{\n\t\t"key": "alt+a",\n\t\t"command": "f"\n\t},\n\t{\n\t\t"key": "alt+e",\n\t\t"command": "e"\n\t},\n\t{\n\t\t"key": "alt+g",\n\t\t"command": "g",\n\t\t"when": "context2"\n\t}\n]' + const actual = await testObject.merge(localContent, remoteContent, baseContent); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, expected); + }); + }); suite('KeybindingsMerge - Conflicts', () => { From 530988357a0bb5ba28edbfad97befa019a312fdb Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 28 Nov 2019 18:07:42 +0100 Subject: [PATCH 039/255] more smart merging --- .../keybinding/common/keybindingsMerge.ts | 163 +++++++++++------- 1 file changed, 98 insertions(+), 65 deletions(-) diff --git a/src/vs/workbench/services/keybinding/common/keybindingsMerge.ts b/src/vs/workbench/services/keybinding/common/keybindingsMerge.ts index 47f3cca8f65..0f3da528a2b 100644 --- a/src/vs/workbench/services/keybinding/common/keybindingsMerge.ts +++ b/src/vs/workbench/services/keybinding/common/keybindingsMerge.ts @@ -19,6 +19,8 @@ interface ICompareResult { } interface IMergeResult { + hasLocalForwarded: boolean; + hasRemoteForwarded: boolean; added: Set; removed: Set; updated: Set; @@ -37,78 +39,36 @@ export class KeybindingsMergeService implements IKeybindingsMergeService { const local = parse(localContent); const remote = parse(remoteContent); const base = baseContent ? parse(baseContent) : null; + const normalizedKeys = this.getNormalizedKeys(local, remote, base); - const normalize = (keybinding: IUserFriendlyKeybinding): IUserFriendlyKeybinding => ({ - ...keybinding, - ...{ - key: this.keybindingsService.resolveUserBinding(keybinding.key).map(part => part.getUserSettingsLabel()).join(' ') - } - }); + let keybindingsMergeResult = this.computeMergeResultByKeybinding(local, remote, base, normalizedKeys); - const normalizedLocal = local.map(keybinding => normalize(keybinding)); - const normalizedRemote = remote.map(keybinding => normalize(keybinding)); - const normalizedBase = base ? base.map(keybinding => normalize(keybinding)) : null; - - const byKeybinding = (keybindings: IUserFriendlyKeybinding[], normalized: IUserFriendlyKeybinding[]) => { - const map: Map = new Map(); - for (let index = 0; index < normalized.length; index++) { - let value = map.get(normalized[index].key); - if (!value) { - value = []; - map.set(normalized[index].key, value); - } - value.push({ keybinding: keybindings[index], normalized: normalized[index] }); - } - return map; - }; - - const localByKeybinding = byKeybinding(local, normalizedLocal); - const remoteByKeybinding = byKeybinding(remote, normalizedRemote); - const baseByKeybinding = base ? byKeybinding(base, normalizedBase!) : null; - - const localToRemoteByKeybinding = this.compareByKeybinding(localByKeybinding, remoteByKeybinding); - if (localToRemoteByKeybinding.added.size === 0 && localToRemoteByKeybinding.removed.size === 0 && localToRemoteByKeybinding.updated.size === 0) { + if (!keybindingsMergeResult.hasLocalForwarded && !keybindingsMergeResult.hasRemoteForwarded) { // No changes found between local and remote. return { mergeContent: localContent, hasChanges: false, hasConflicts: false }; } - const baseToLocalByKeybinding = baseByKeybinding ? this.compareByKeybinding(baseByKeybinding, localByKeybinding) : { added: keys(localByKeybinding).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; - if (baseToLocalByKeybinding.added.size === 0 && baseToLocalByKeybinding.removed.size === 0 && baseToLocalByKeybinding.updated.size === 0) { - // Remote has moved forward and local has not. Return remote + if (!keybindingsMergeResult.hasLocalForwarded && keybindingsMergeResult.hasRemoteForwarded) { return { mergeContent: remoteContent, hasChanges: true, hasConflicts: false }; } - const baseToRemoteByKeybinding = baseByKeybinding ? this.compareByKeybinding(baseByKeybinding, remoteByKeybinding) : { added: keys(remoteByKeybinding).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; - if (baseToRemoteByKeybinding.added.size === 0 && baseToRemoteByKeybinding.removed.size === 0 && baseToRemoteByKeybinding.updated.size === 0) { + if (keybindingsMergeResult.hasLocalForwarded && !keybindingsMergeResult.hasRemoteForwarded) { // Local has moved forward and remote has not. Return local. return { mergeContent: localContent, hasChanges: true, hasConflicts: false }; } // Both local and remote has moved forward. - const byCommand = (keybindings: IUserFriendlyKeybinding[], normalized: IUserFriendlyKeybinding[]) => { - const map: Map = new Map(); - for (let index = 0; index < normalized.length; index++) { - const command = normalized[index].command[0] === '-' ? normalized[index].command.substring(1) : normalized[index].command; - let value = map.get(command); - if (!value) { - value = []; - map.set(command, value); - } - value.push({ keybinding: keybindings[index], normalized: normalized[index] }); - } - return map; - }; - const localByCommand = byCommand(local, normalizedLocal); - const remoteByCommand = byCommand(remote, normalizedRemote); - const baseByCommand = base ? byCommand(base, normalizedBase!) : null; - const localToRemoteByCommand = this.compareByCommand(localByCommand, remoteByCommand); - const baseToLocalByCommand = baseByCommand ? this.compareByCommand(baseByCommand, localByCommand) : { added: keys(localByCommand).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; - const baseToRemoteByCommand = baseByCommand ? this.compareByCommand(baseByCommand, remoteByCommand) : { added: keys(remoteByCommand).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; + const localByCommand = this.byCommand(local); + const remoteByCommand = this.byCommand(remote); + const baseByCommand = base ? this.byCommand(base) : null; + const localToRemoteByCommand = this.compareByCommand(localByCommand, remoteByCommand, normalizedKeys); + const baseToLocalByCommand = baseByCommand ? this.compareByCommand(baseByCommand, localByCommand, normalizedKeys) : { added: keys(localByCommand).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; + const baseToRemoteByCommand = baseByCommand ? this.compareByCommand(baseByCommand, remoteByCommand, normalizedKeys) : { added: keys(remoteByCommand).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; const commandsMergeResult = this.computeMergeResult(localToRemoteByCommand, baseToLocalByCommand, baseToRemoteByCommand); - const keybindingsMergeResult = this.computeMergeResult(localToRemoteByKeybinding, baseToLocalByKeybinding, baseToRemoteByKeybinding); const eol = contentUtil.getEol(localContent); let mergeContent = localContent; + let mergeContentChanged = false; // Removed commands in Remote for (const command of values(commandsMergeResult.removed)) { @@ -116,8 +76,13 @@ export class KeybindingsMergeService implements IKeybindingsMergeService { continue; } mergeContent = this.removeKeybindings(mergeContent, eol, command); + mergeContentChanged = true; } + if (mergeContentChanged) { + keybindingsMergeResult = this.computeMergeResultByKeybinding(local, remote, parse(mergeContent), normalizedKeys); + } + mergeContentChanged = false; // Added commands in remote for (const command of values(commandsMergeResult.added)) { if (commandsMergeResult.conflicts.has(command)) { @@ -125,13 +90,17 @@ export class KeybindingsMergeService implements IKeybindingsMergeService { } const keybindings = remoteByCommand.get(command)!; // Ignore negated commands - if (keybindings.some(({ normalized }) => normalized.command !== `-${command}` && keybindingsMergeResult.conflicts.has(normalized.key))) { + if (keybindings.some(keybinding => keybinding.command !== `-${command}` && keybindingsMergeResult.conflicts.has(normalizedKeys.get(keybinding.key)!))) { commandsMergeResult.conflicts.add(command); continue; } - mergeContent = this.addKeybindings(mergeContent, eol, keybindings.map(({ keybinding }) => keybinding)); + mergeContent = this.addKeybindings(mergeContent, eol, keybindings); + mergeContentChanged = true; } + if (mergeContentChanged) { + keybindingsMergeResult = this.computeMergeResultByKeybinding(local, remote, parse(mergeContent), normalizedKeys); + } // Updated commands in Remote for (const command of values(commandsMergeResult.updated)) { if (commandsMergeResult.conflicts.has(command)) { @@ -139,11 +108,11 @@ export class KeybindingsMergeService implements IKeybindingsMergeService { } const keybindings = remoteByCommand.get(command)!; // Ignore negated commands - if (keybindings.some(({ normalized }) => normalized.command !== `-${command}` && keybindingsMergeResult.conflicts.has(normalized.key))) { + if (keybindings.some(keybinding => keybinding.command !== `-${command}` && keybindingsMergeResult.conflicts.has(normalizedKeys.get(keybinding.key)!))) { commandsMergeResult.conflicts.add(command); continue; } - mergeContent = this.updateKeybindings(mergeContent, eol, command, keybindings.map(({ keybinding }) => keybinding)); + mergeContent = this.updateKeybindings(mergeContent, eol, command, keybindings); } const hasConflicts = commandsMergeResult.conflicts.size > 0; @@ -158,7 +127,7 @@ export class KeybindingsMergeService implements IKeybindingsMergeService { return { mergeContent, hasChanges: true, hasConflicts }; } - private computeMergeResult(localToRemote: ICompareResult, baseToLocal: ICompareResult, baseToRemote: ICompareResult): IMergeResult { + private computeMergeResult(localToRemote: ICompareResult, baseToLocal: ICompareResult, baseToRemote: ICompareResult): { added: Set, removed: Set, updated: Set, conflicts: Set } { const added: Set = new Set(); const removed: Set = new Set(); const updated: Set = new Set(); @@ -249,7 +218,71 @@ export class KeybindingsMergeService implements IKeybindingsMergeService { return { added, removed, updated, conflicts }; } - private compareByKeybinding(from: Map, to: Map): ICompareResult { + private getNormalizedKeys(local: IUserFriendlyKeybinding[], remote: IUserFriendlyKeybinding[], base: IUserFriendlyKeybinding[] | null): Map { + const keys = new Map(); + for (const keybinding of [...local, ...remote, ...(base || [])]) { + keys.set(keybinding.key, this.keybindingsService.resolveUserBinding(keybinding.key).map(part => part.getUserSettingsLabel()).join(' ')); + } + return keys; + } + + private computeMergeResultByKeybinding(local: IUserFriendlyKeybinding[], remote: IUserFriendlyKeybinding[], base: IUserFriendlyKeybinding[] | null, normalizedKeys: Map): IMergeResult { + const empty = new Set(); + const localByKeybinding = this.byKeybinding(local, normalizedKeys); + const remoteByKeybinding = this.byKeybinding(remote, normalizedKeys); + const baseByKeybinding = base ? this.byKeybinding(base, normalizedKeys) : null; + + const localToRemoteByKeybinding = this.compareByKeybinding(localByKeybinding, remoteByKeybinding); + if (localToRemoteByKeybinding.added.size === 0 && localToRemoteByKeybinding.removed.size === 0 && localToRemoteByKeybinding.updated.size === 0) { + return { hasLocalForwarded: false, hasRemoteForwarded: false, added: empty, removed: empty, updated: empty, conflicts: empty }; + } + + const baseToLocalByKeybinding = baseByKeybinding ? this.compareByKeybinding(baseByKeybinding, localByKeybinding) : { added: keys(localByKeybinding).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; + if (baseToLocalByKeybinding.added.size === 0 && baseToLocalByKeybinding.removed.size === 0 && baseToLocalByKeybinding.updated.size === 0) { + // Remote has moved forward and local has not. + return { hasLocalForwarded: false, hasRemoteForwarded: true, added: empty, removed: empty, updated: empty, conflicts: empty }; + } + + const baseToRemoteByKeybinding = baseByKeybinding ? this.compareByKeybinding(baseByKeybinding, remoteByKeybinding) : { added: keys(remoteByKeybinding).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; + if (baseToRemoteByKeybinding.added.size === 0 && baseToRemoteByKeybinding.removed.size === 0 && baseToRemoteByKeybinding.updated.size === 0) { + return { hasLocalForwarded: true, hasRemoteForwarded: false, added: empty, removed: empty, updated: empty, conflicts: empty }; + } + + const { added, removed, updated, conflicts } = this.computeMergeResult(localToRemoteByKeybinding, baseToLocalByKeybinding, baseToRemoteByKeybinding); + return { hasLocalForwarded: true, hasRemoteForwarded: true, added, removed, updated, conflicts }; + } + + private byKeybinding(keybindings: IUserFriendlyKeybinding[], keys: Map) { + const map: Map = new Map(); + for (const keybinding of keybindings) { + const key = keys.get(keybinding.key)!; + let value = map.get(key); + if (!value) { + value = []; + map.set(key, value); + } + value.push(keybinding); + + } + return map; + } + + private byCommand(keybindings: IUserFriendlyKeybinding[]): Map { + const map: Map = new Map(); + for (const keybinding of keybindings) { + const command = keybinding.command[0] === '-' ? keybinding.command.substring(1) : keybinding.command; + let value = map.get(command); + if (!value) { + value = []; + map.set(command, value); + } + value.push(keybinding); + } + return map; + } + + + private compareByKeybinding(from: Map, to: Map): ICompareResult { const fromKeys = keys(from); const toKeys = keys(to); const added = toKeys.filter(key => fromKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); @@ -260,8 +293,8 @@ export class KeybindingsMergeService implements IKeybindingsMergeService { if (removed.has(key)) { continue; } - const value1: IUserFriendlyKeybinding[] = from.get(key)!.map(({ normalized }) => normalized); - const value2: IUserFriendlyKeybinding[] = to.get(key)!.map(({ normalized }) => normalized); + const value1: IUserFriendlyKeybinding[] = from.get(key)!.map(keybinding => ({ ...keybinding, ...{ key } })); + const value2: IUserFriendlyKeybinding[] = to.get(key)!.map(keybinding => ({ ...keybinding, ...{ key } })); if (!equals(value1, value2, (a, b) => this.isSameKeybinding(a, b))) { updated.add(key); } @@ -270,7 +303,7 @@ export class KeybindingsMergeService implements IKeybindingsMergeService { return { added, removed, updated }; } - private compareByCommand(from: Map, to: Map): ICompareResult { + private compareByCommand(from: Map, to: Map, normalizedKeys: Map): ICompareResult { const fromKeys = keys(from); const toKeys = keys(to); const added = toKeys.filter(key => fromKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); @@ -281,8 +314,8 @@ export class KeybindingsMergeService implements IKeybindingsMergeService { if (removed.has(key)) { continue; } - const value1: IUserFriendlyKeybinding[] = from.get(key)!.map(({ normalized }) => normalized); - const value2: IUserFriendlyKeybinding[] = to.get(key)!.map(({ normalized }) => normalized); + const value1: IUserFriendlyKeybinding[] = from.get(key)!.map(keybinding => ({ ...keybinding, ...{ key: normalizedKeys.get(keybinding.key)! } })); + const value2: IUserFriendlyKeybinding[] = to.get(key)!.map(keybinding => ({ ...keybinding, ...{ key: normalizedKeys.get(keybinding.key)! } })); if (!this.areSameKeybindingsWithSameCommand(value1, value2)) { updated.add(key); } From 02bd95c9066294b8f387a57203172c7000333acb Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 28 Nov 2019 20:20:35 +0100 Subject: [PATCH 040/255] refactor keybindings merge --- .../sharedProcess/sharedProcessMain.ts | 7 +- .../userDataSync/common/keybindingsMerge.ts | 374 +++++++++++++++++ .../userDataSync/common/keybindingsSync.ts | 8 +- .../userDataSync/common/keybindingsSyncIpc.ts | 15 +- .../userDataSync/common/userDataSync.ts | 7 +- .../test/common}/keybindingsMerge.test.ts | 94 ++--- .../userDataSync.contribution.ts | 8 +- .../keybinding/common/keybindingsMerge.ts | 392 ------------------ .../userDataSync/common/keybindingsMerge.ts | 32 ++ src/vs/workbench/workbench.common.main.ts | 2 +- 10 files changed, 477 insertions(+), 462 deletions(-) create mode 100644 src/vs/platform/userDataSync/common/keybindingsMerge.ts rename src/vs/{workbench/services/keybinding/test/electron-browser => platform/userDataSync/test/common}/keybindingsMerge.test.ts (87%) delete mode 100644 src/vs/workbench/services/keybinding/common/keybindingsMerge.ts create mode 100644 src/vs/workbench/services/userDataSync/common/keybindingsMerge.ts diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 60766bcc4a5..822ba16de3c 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -50,7 +50,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { DiskFileSystemProvider } from 'vs/platform/files/electron-browser/diskFileSystemProvider'; import { Schemas } from 'vs/base/common/network'; import { IProductService } from 'vs/platform/product/common/productService'; -import { IUserDataSyncService, IUserDataSyncStoreService, ISettingsMergeService, registerConfiguration, IUserDataSyncLogService, IKeybindingsMergeService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, IUserDataSyncStoreService, ISettingsMergeService, registerConfiguration, IUserDataSyncLogService, IUserKeybindingsResolverService } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncService, UserDataAutoSync } from 'vs/platform/userDataSync/common/userDataSyncService'; import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; import { UserDataSyncChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc'; @@ -63,7 +63,7 @@ import { AuthTokenService } from 'vs/platform/auth/electron-browser/authTokenSer import { AuthTokenChannel } from 'vs/platform/auth/common/authTokenIpc'; import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; import { KeytarCredentialsService } from 'vs/platform/credentials/node/credentialsService'; -import { KeybindingsMergeChannelClient } from 'vs/platform/userDataSync/common/keybindingsSyncIpc'; +import { UserKeybindingsResolverServiceClient } from 'vs/platform/userDataSync/common/keybindingsSyncIpc'; export interface ISharedProcessConfiguration { readonly machineId: string; @@ -187,8 +187,7 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat services.set(IUserDataSyncLogService, new SyncDescriptor(UserDataSyncLogService)); const settingsMergeChannel = server.getChannel('settingsMerge', activeWindowRouter); services.set(ISettingsMergeService, new SettingsMergeChannelClient(settingsMergeChannel)); - const keybindingsMergeChannel = server.getChannel('keybindingsMerge', activeWindowRouter); - services.set(IKeybindingsMergeService, new KeybindingsMergeChannelClient(keybindingsMergeChannel)); + services.set(IUserKeybindingsResolverService, new UserKeybindingsResolverServiceClient(server.getChannel('userKeybindingsResolver', activeWindowRouter))); services.set(IUserDataSyncStoreService, new SyncDescriptor(UserDataSyncStoreService)); services.set(IUserDataSyncService, new SyncDescriptor(UserDataSyncService)); registerConfiguration(); diff --git a/src/vs/platform/userDataSync/common/keybindingsMerge.ts b/src/vs/platform/userDataSync/common/keybindingsMerge.ts new file mode 100644 index 00000000000..84c6a15062f --- /dev/null +++ b/src/vs/platform/userDataSync/common/keybindingsMerge.ts @@ -0,0 +1,374 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as objects from 'vs/base/common/objects'; +import { parse } from 'vs/base/common/json'; +import { values, keys } from 'vs/base/common/map'; +import { IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding'; +import { firstIndex as findFirstIndex, equals } from 'vs/base/common/arrays'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import * as contentUtil from 'vs/platform/userDataSync/common/content'; +import { IStringDictionary } from 'vs/base/common/collections'; + +interface ICompareResult { + added: Set; + removed: Set; + updated: Set; +} + +interface IMergeResult { + hasLocalForwarded: boolean; + hasRemoteForwarded: boolean; + added: Set; + removed: Set; + updated: Set; + conflicts: Set; +} + +export function merge(localContent: string, remoteContent: string, baseContent: string | null, normalizedKeys: IStringDictionary): { mergeContent: string, hasChanges: boolean, hasConflicts: boolean } { + const local = parse(localContent); + const remote = parse(remoteContent); + const base = baseContent ? parse(baseContent) : null; + + let keybindingsMergeResult = computeMergeResultByKeybinding(local, remote, base, normalizedKeys); + + if (!keybindingsMergeResult.hasLocalForwarded && !keybindingsMergeResult.hasRemoteForwarded) { + // No changes found between local and remote. + return { mergeContent: localContent, hasChanges: false, hasConflicts: false }; + } + + if (!keybindingsMergeResult.hasLocalForwarded && keybindingsMergeResult.hasRemoteForwarded) { + return { mergeContent: remoteContent, hasChanges: true, hasConflicts: false }; + } + + if (keybindingsMergeResult.hasLocalForwarded && !keybindingsMergeResult.hasRemoteForwarded) { + // Local has moved forward and remote has not. Return local. + return { mergeContent: localContent, hasChanges: true, hasConflicts: false }; + } + + // Both local and remote has moved forward. + const localByCommand = byCommand(local); + const remoteByCommand = byCommand(remote); + const baseByCommand = base ? byCommand(base) : null; + const localToRemoteByCommand = compareByCommand(localByCommand, remoteByCommand, normalizedKeys); + const baseToLocalByCommand = baseByCommand ? compareByCommand(baseByCommand, localByCommand, normalizedKeys) : { added: keys(localByCommand).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; + const baseToRemoteByCommand = baseByCommand ? compareByCommand(baseByCommand, remoteByCommand, normalizedKeys) : { added: keys(remoteByCommand).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; + + const commandsMergeResult = computeMergeResult(localToRemoteByCommand, baseToLocalByCommand, baseToRemoteByCommand); + const eol = contentUtil.getEol(localContent); + let mergeContent = localContent; + let mergeContentChanged = false; + + // Removed commands in Remote + for (const command of values(commandsMergeResult.removed)) { + if (commandsMergeResult.conflicts.has(command)) { + continue; + } + mergeContent = removeKeybindings(mergeContent, eol, command); + mergeContentChanged = true; + } + + if (mergeContentChanged) { + keybindingsMergeResult = computeMergeResultByKeybinding(local, remote, parse(mergeContent), normalizedKeys); + } + mergeContentChanged = false; + // Added commands in remote + for (const command of values(commandsMergeResult.added)) { + if (commandsMergeResult.conflicts.has(command)) { + continue; + } + const keybindings = remoteByCommand.get(command)!; + // Ignore negated commands + if (keybindings.some(keybinding => keybinding.command !== `-${command}` && keybindingsMergeResult.conflicts.has(normalizedKeys[keybinding.key]))) { + commandsMergeResult.conflicts.add(command); + continue; + } + mergeContent = addKeybindings(mergeContent, eol, keybindings); + mergeContentChanged = true; + } + + if (mergeContentChanged) { + keybindingsMergeResult = computeMergeResultByKeybinding(local, remote, parse(mergeContent), normalizedKeys); + } + // Updated commands in Remote + for (const command of values(commandsMergeResult.updated)) { + if (commandsMergeResult.conflicts.has(command)) { + continue; + } + const keybindings = remoteByCommand.get(command)!; + // Ignore negated commands + if (keybindings.some(keybinding => keybinding.command !== `-${command}` && keybindingsMergeResult.conflicts.has(normalizedKeys[keybinding.key]))) { + commandsMergeResult.conflicts.add(command); + continue; + } + mergeContent = updateKeybindings(mergeContent, eol, command, keybindings); + } + + const hasConflicts = commandsMergeResult.conflicts.size > 0; + if (hasConflicts) { + mergeContent = `<<<<<<< local${eol}` + + mergeContent + + `${eol}=======${eol}` + + remoteContent + + `${eol}>>>>>>> remote`; + } + + return { mergeContent, hasChanges: true, hasConflicts }; +} + +function computeMergeResult(localToRemote: ICompareResult, baseToLocal: ICompareResult, baseToRemote: ICompareResult): { added: Set, removed: Set, updated: Set, conflicts: Set } { + const added: Set = new Set(); + const removed: Set = new Set(); + const updated: Set = new Set(); + const conflicts: Set = new Set(); + + // Removed keys in Local + for (const key of values(baseToLocal.removed)) { + // Got updated in remote + if (baseToRemote.updated.has(key)) { + conflicts.add(key); + } + } + + // Removed keys in Remote + for (const key of values(baseToRemote.removed)) { + if (conflicts.has(key)) { + continue; + } + // Got updated in local + if (baseToLocal.updated.has(key)) { + conflicts.add(key); + } else { + // remove the key + removed.add(key); + } + } + + // Added keys in Local + for (const key of values(baseToLocal.added)) { + if (conflicts.has(key)) { + continue; + } + // Got added in remote + if (baseToRemote.added.has(key)) { + // Has different value + if (localToRemote.updated.has(key)) { + conflicts.add(key); + } + } + } + + // Added keys in remote + for (const key of values(baseToRemote.added)) { + if (conflicts.has(key)) { + continue; + } + // Got added in local + if (baseToLocal.added.has(key)) { + // Has different value + if (localToRemote.updated.has(key)) { + conflicts.add(key); + } + } else { + added.add(key); + } + } + + // Updated keys in Local + for (const key of values(baseToLocal.updated)) { + if (conflicts.has(key)) { + continue; + } + // Got updated in remote + if (baseToRemote.updated.has(key)) { + // Has different value + if (localToRemote.updated.has(key)) { + conflicts.add(key); + } + } + } + + // Updated keys in Remote + for (const key of values(baseToRemote.updated)) { + if (conflicts.has(key)) { + continue; + } + // Got updated in local + if (baseToLocal.updated.has(key)) { + // Has different value + if (localToRemote.updated.has(key)) { + conflicts.add(key); + } + } else { + // updated key + updated.add(key); + } + } + return { added, removed, updated, conflicts }; +} + +function computeMergeResultByKeybinding(local: IUserFriendlyKeybinding[], remote: IUserFriendlyKeybinding[], base: IUserFriendlyKeybinding[] | null, normalizedKeys: IStringDictionary): IMergeResult { + const empty = new Set(); + const localByKeybinding = byKeybinding(local, normalizedKeys); + const remoteByKeybinding = byKeybinding(remote, normalizedKeys); + const baseByKeybinding = base ? byKeybinding(base, normalizedKeys) : null; + + const localToRemoteByKeybinding = compareByKeybinding(localByKeybinding, remoteByKeybinding); + if (localToRemoteByKeybinding.added.size === 0 && localToRemoteByKeybinding.removed.size === 0 && localToRemoteByKeybinding.updated.size === 0) { + return { hasLocalForwarded: false, hasRemoteForwarded: false, added: empty, removed: empty, updated: empty, conflicts: empty }; + } + + const baseToLocalByKeybinding = baseByKeybinding ? compareByKeybinding(baseByKeybinding, localByKeybinding) : { added: keys(localByKeybinding).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; + if (baseToLocalByKeybinding.added.size === 0 && baseToLocalByKeybinding.removed.size === 0 && baseToLocalByKeybinding.updated.size === 0) { + // Remote has moved forward and local has not. + return { hasLocalForwarded: false, hasRemoteForwarded: true, added: empty, removed: empty, updated: empty, conflicts: empty }; + } + + const baseToRemoteByKeybinding = baseByKeybinding ? compareByKeybinding(baseByKeybinding, remoteByKeybinding) : { added: keys(remoteByKeybinding).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; + if (baseToRemoteByKeybinding.added.size === 0 && baseToRemoteByKeybinding.removed.size === 0 && baseToRemoteByKeybinding.updated.size === 0) { + return { hasLocalForwarded: true, hasRemoteForwarded: false, added: empty, removed: empty, updated: empty, conflicts: empty }; + } + + const { added, removed, updated, conflicts } = computeMergeResult(localToRemoteByKeybinding, baseToLocalByKeybinding, baseToRemoteByKeybinding); + return { hasLocalForwarded: true, hasRemoteForwarded: true, added, removed, updated, conflicts }; +} + +function byKeybinding(keybindings: IUserFriendlyKeybinding[], keys: IStringDictionary) { + const map: Map = new Map(); + for (const keybinding of keybindings) { + const key = keys[keybinding.key]; + let value = map.get(key); + if (!value) { + value = []; + map.set(key, value); + } + value.push(keybinding); + + } + return map; +} + +function byCommand(keybindings: IUserFriendlyKeybinding[]): Map { + const map: Map = new Map(); + for (const keybinding of keybindings) { + const command = keybinding.command[0] === '-' ? keybinding.command.substring(1) : keybinding.command; + let value = map.get(command); + if (!value) { + value = []; + map.set(command, value); + } + value.push(keybinding); + } + return map; +} + + +function compareByKeybinding(from: Map, to: Map): ICompareResult { + const fromKeys = keys(from); + const toKeys = keys(to); + const added = toKeys.filter(key => fromKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); + const removed = fromKeys.filter(key => toKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); + const updated: Set = new Set(); + + for (const key of fromKeys) { + if (removed.has(key)) { + continue; + } + const value1: IUserFriendlyKeybinding[] = from.get(key)!.map(keybinding => ({ ...keybinding, ...{ key } })); + const value2: IUserFriendlyKeybinding[] = to.get(key)!.map(keybinding => ({ ...keybinding, ...{ key } })); + if (!equals(value1, value2, (a, b) => isSameKeybinding(a, b))) { + updated.add(key); + } + } + + return { added, removed, updated }; +} + +function compareByCommand(from: Map, to: Map, normalizedKeys: IStringDictionary): ICompareResult { + const fromKeys = keys(from); + const toKeys = keys(to); + const added = toKeys.filter(key => fromKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); + const removed = fromKeys.filter(key => toKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); + const updated: Set = new Set(); + + for (const key of fromKeys) { + if (removed.has(key)) { + continue; + } + const value1: IUserFriendlyKeybinding[] = from.get(key)!.map(keybinding => ({ ...keybinding, ...{ key: normalizedKeys[keybinding.key] } })); + const value2: IUserFriendlyKeybinding[] = to.get(key)!.map(keybinding => ({ ...keybinding, ...{ key: normalizedKeys[keybinding.key] } })); + if (!areSameKeybindingsWithSameCommand(value1, value2)) { + updated.add(key); + } + } + + return { added, removed, updated }; +} + +function areSameKeybindingsWithSameCommand(value1: IUserFriendlyKeybinding[], value2: IUserFriendlyKeybinding[]): boolean { + // Compare entries adding keybindings + if (!equals(value1.filter(({ command }) => command[0] !== '-'), value2.filter(({ command }) => command[0] !== '-'), (a, b) => isSameKeybinding(a, b))) { + return false; + } + // Compare entries removing keybindings + if (!equals(value1.filter(({ command }) => command[0] === '-'), value2.filter(({ command }) => command[0] === '-'), (a, b) => isSameKeybinding(a, b))) { + return false; + } + return true; +} + +function isSameKeybinding(a: IUserFriendlyKeybinding, b: IUserFriendlyKeybinding): boolean { + if (a.command !== b.command) { + return false; + } + if (a.key !== b.key) { + return false; + } + const whenA = ContextKeyExpr.deserialize(a.when); + const whenB = ContextKeyExpr.deserialize(b.when); + if ((whenA && !whenB) || (!whenA && whenB)) { + return false; + } + if (whenA && whenB && !whenA.equals(whenB)) { + return false; + } + if (!objects.equals(a.args, b.args)) { + return false; + } + return true; +} + +function addKeybindings(content: string, eol: string, keybindings: IUserFriendlyKeybinding[]): string { + for (const keybinding of keybindings) { + content = contentUtil.edit(content, eol, [-1], keybinding); + } + return content; +} + +function removeKeybindings(content: string, eol: string, command: string): string { + const keybindings = parse(content); + for (let index = keybindings.length - 1; index >= 0; index--) { + if (keybindings[index].command === command || keybindings[index].command === `-${command}`) { + content = contentUtil.edit(content, eol, [index], undefined); + } + } + return content; +} + +function updateKeybindings(content: string, eol: string, command: string, keybindings: IUserFriendlyKeybinding[]): string { + const allKeybindings = parse(content); + const location = findFirstIndex(allKeybindings, keybinding => keybinding.command === command || keybinding.command === `-${command}`); + // Remove all entries with this command + for (let index = allKeybindings.length - 1; index >= 0; index--) { + if (allKeybindings[index].command === command || allKeybindings[index].command === `-${command}`) { + content = contentUtil.edit(content, eol, [index], undefined); + } + } + // add all entries at the same location where the entry with this command was located. + for (let index = keybindings.length - 1; index >= 0; index--) { + content = contentUtil.edit(content, eol, [location], keybindings[index]); + } + return content; +} diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index 4da5fadbe44..da6c85b1ab7 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -5,7 +5,8 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IFileService, FileSystemProviderErrorCode, FileSystemProviderError, IFileContent } from 'vs/platform/files/common/files'; -import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, ISynchroniser, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IKeybindingsMergeService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, ISynchroniser, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserKeybindingsResolverService } from 'vs/platform/userDataSync/common/userDataSync'; +import { merge } from 'vs/platform/userDataSync/common/keybindingsMerge'; import { VSBuffer } from 'vs/base/common/buffer'; import { parse, ParseError } from 'vs/base/common/json'; import { localize } from 'vs/nls'; @@ -48,7 +49,7 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser @IConfigurationService private readonly configurationService: IConfigurationService, @IFileService private readonly fileService: IFileService, @IEnvironmentService private readonly environmentService: IEnvironmentService, - @IKeybindingsMergeService private readonly keybindingsMergeService: IKeybindingsMergeService, + @IUserKeybindingsResolverService private readonly userKeybindingsResolverService: IUserKeybindingsResolverService, ) { super(); this.lastSyncKeybindingsResource = joinPath(this.environmentService.userRoamingDataHome, '.lastSyncKeybindings.json'); @@ -220,7 +221,8 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser || lastSyncData.content !== remoteContent // Remote has forwarded ) { this.logService.trace('Keybindings: Merging remote keybindings with local keybindings...'); - const result = await this.keybindingsMergeService.merge(localContent, remoteContent, lastSyncData ? lastSyncData.content : null); + const keys = await this.userKeybindingsResolverService.resolveUserKeybindings(localContent, remoteContent, lastSyncData ? lastSyncData.content : null); + const result = merge(localContent, remoteContent, lastSyncData ? lastSyncData.content : null, keys); // Sync only if there are changes if (result.hasChanges) { hasLocalChanged = result.mergeContent !== localContent; diff --git a/src/vs/platform/userDataSync/common/keybindingsSyncIpc.ts b/src/vs/platform/userDataSync/common/keybindingsSyncIpc.ts index 0bfa0de8f6a..c4c852604a9 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSyncIpc.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSyncIpc.ts @@ -5,11 +5,12 @@ import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { Event } from 'vs/base/common/event'; -import { IKeybindingsMergeService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserKeybindingsResolverService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IStringDictionary } from 'vs/base/common/collections'; -export class KeybindingsMergeChannel implements IServerChannel { +export class UserKeybindingsResolverServiceChannel implements IServerChannel { - constructor(private readonly service: IKeybindingsMergeService) { } + constructor(private readonly service: IUserKeybindingsResolverService) { } listen(_: unknown, event: string): Event { throw new Error(`Event not found: ${event}`); @@ -17,21 +18,21 @@ export class KeybindingsMergeChannel implements IServerChannel { call(context: any, command: string, args?: any): Promise { switch (command) { - case 'merge': return this.service.merge(args[0], args[1], args[2]); + case 'resolveUserKeybindings': return this.service.resolveUserKeybindings(args[0], args[1], args[2]); } throw new Error('Invalid call'); } } -export class KeybindingsMergeChannelClient implements IKeybindingsMergeService { +export class UserKeybindingsResolverServiceClient implements IUserKeybindingsResolverService { _serviceBrand: undefined; constructor(private readonly channel: IChannel) { } - merge(localContent: string, remoteContent: string, baseContent: string | null): Promise<{ mergeContent: string, hasChanges: boolean, hasConflicts: boolean }> { - return this.channel.call('merge', [localContent, remoteContent, baseContent]); + async resolveUserKeybindings(localContent: string, remoteContent: string, baseContent: string | null): Promise> { + return this.channel.call('resolveUserKeybindings', [localContent, remoteContent, baseContent]); } } diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index c17f0f7746d..660cae2d320 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -15,6 +15,7 @@ import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/plat import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { ILogService } from 'vs/platform/log/common/log'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IStringDictionary } from 'vs/base/common/collections'; const CONFIGURATION_SYNC_STORE_KEY = 'configurationSync.store'; @@ -180,13 +181,13 @@ export interface ISettingsMergeService { } -export const IKeybindingsMergeService = createDecorator('IKeybindingsMergeService'); +export const IUserKeybindingsResolverService = createDecorator('IUserKeybindingsResolverService'); -export interface IKeybindingsMergeService { +export interface IUserKeybindingsResolverService { _serviceBrand: undefined; - merge(localContent: string, remoteContent: string, baseContent: string | null): Promise<{ mergeContent: string, hasChanges: boolean, hasConflicts: boolean }>; + resolveUserKeybindings(localContent: string, remoteContent: string, baseContent: string | null): Promise>; } diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingsMerge.test.ts b/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts similarity index 87% rename from src/vs/workbench/services/keybinding/test/electron-browser/keybindingsMerge.test.ts rename to src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts index 9529d3150a8..e3ba6d50ee7 100644 --- a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingsMerge.test.ts +++ b/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts @@ -4,30 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { OS } from 'vs/base/common/platform'; -import { KeybindingsMergeService } from 'vs/workbench/services/keybinding/common/keybindingsMerge'; -import { MockKeybindingService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; -import { ResolvedKeybinding, ChordKeybinding, SimpleKeybinding } from 'vs/base/common/keyCodes'; -import { KeybindingParser } from 'vs/base/common/keybindingParser'; -import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; - -let testObject: KeybindingsMergeService; - -suiteSetup(() => { - testObject = new KeybindingsMergeService(new class extends MockKeybindingService { - resolveUserBinding(userBinding: string): ResolvedKeybinding[] { - const parts = KeybindingParser.parseUserBinding(userBinding); - return [new USLayoutResolvedKeybinding(new ChordKeybinding(parts), OS)]; - } - }); -}); +import { merge } from 'vs/platform/userDataSync/common/keybindingsMerge'; +import { IStringDictionary } from 'vs/base/common/collections'; +import { IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding'; +import { parse } from 'vs/base/common/json'; suite('KeybindingsMerge - No Conflicts', () => { test('merge when local and remote are same with one entry', async () => { const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const actual = await testObject.merge(localContent, remoteContent, null); + const actual = mergeKeybindings(localContent, remoteContent, null); assert.ok(!actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); @@ -36,7 +23,7 @@ suite('KeybindingsMerge - No Conflicts', () => { test('merge when local and remote are same with similar when contexts', async () => { const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: '!editorReadonly && editorTextFocus' }]); - const actual = await testObject.merge(localContent, remoteContent, null); + const actual = mergeKeybindings(localContent, remoteContent, null); assert.ok(!actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); @@ -51,7 +38,7 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+a', command: 'a', when: 'editorTextFocus' }, { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' } ]); - const actual = await testObject.merge(localContent, remoteContent, null); + const actual = mergeKeybindings(localContent, remoteContent, null); assert.ok(!actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); @@ -68,7 +55,7 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+d', command: '-a' }, { key: 'cmd+c', command: 'b', args: { text: '`' } } ]); - const actual = await testObject.merge(localContent, remoteContent, null); + const actual = mergeKeybindings(localContent, remoteContent, null); assert.ok(!actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); @@ -89,7 +76,7 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+d', command: '-a' }, { key: 'cmd+c', command: 'b', args: { text: '`' } } ]); - const actual = await testObject.merge(localContent, remoteContent, baseContent); + const actual = mergeKeybindings(localContent, remoteContent, baseContent); assert.ok(!actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); @@ -106,7 +93,7 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, ]); - const actual = await testObject.merge(localContent, remoteContent, null); + const actual = mergeKeybindings(localContent, remoteContent, null); assert.ok(!actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); @@ -123,7 +110,7 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'cmd+c', command: 'b', args: { text: '`' } }, ]); - const actual = await testObject.merge(localContent, remoteContent, null); + const actual = mergeKeybindings(localContent, remoteContent, null); assert.ok(!actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); @@ -139,7 +126,7 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+d', command: '-a' }, { key: 'cmd+c', command: 'b', args: { text: '`' } }, ]); - const actual = await testObject.merge(localContent, remoteContent, null); + const actual = mergeKeybindings(localContent, remoteContent, null); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, remoteContent); @@ -156,7 +143,7 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'cmd+c', command: 'b', args: { text: '`' } }, { key: 'cmd+d', command: 'c' }, ]); - const actual = await testObject.merge(localContent, remoteContent, null); + const actual = mergeKeybindings(localContent, remoteContent, null); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, remoteContent); @@ -173,7 +160,7 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'cmd+c', command: 'b', args: { text: '`' } }, { key: 'cmd+d', command: 'c' }, ]); - const actual = await testObject.merge(localContent, remoteContent, localContent); + const actual = mergeKeybindings(localContent, remoteContent, localContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, remoteContent); @@ -189,7 +176,7 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, ]); - const actual = await testObject.merge(localContent, remoteContent, localContent); + const actual = mergeKeybindings(localContent, remoteContent, localContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, remoteContent); @@ -203,7 +190,7 @@ suite('KeybindingsMerge - No Conflicts', () => { const remoteContent = stringify([ { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, ]); - const actual = await testObject.merge(localContent, remoteContent, localContent); + const actual = mergeKeybindings(localContent, remoteContent, localContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, remoteContent); @@ -216,7 +203,7 @@ suite('KeybindingsMerge - No Conflicts', () => { const remoteContent = stringify([ { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, ]); - const actual = await testObject.merge(localContent, remoteContent, localContent); + const actual = mergeKeybindings(localContent, remoteContent, localContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, remoteContent); @@ -235,7 +222,7 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+d', command: 'b' }, { key: 'cmd+d', command: 'a' }, ]); - const actual = await testObject.merge(localContent, remoteContent, localContent); + const actual = mergeKeybindings(localContent, remoteContent, localContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, remoteContent); @@ -258,7 +245,7 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'cmd+d', command: 'c', when: 'context1' }, { key: 'cmd+c', command: '-c' }, ]); - const actual = await testObject.merge(localContent, remoteContent, localContent); + const actual = mergeKeybindings(localContent, remoteContent, localContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, remoteContent); @@ -274,7 +261,7 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, ]); - const actual = await testObject.merge(localContent, remoteContent, null); + const actual = mergeKeybindings(localContent, remoteContent, null); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); @@ -291,7 +278,7 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, ]); - const actual = await testObject.merge(localContent, remoteContent, null); + const actual = mergeKeybindings(localContent, remoteContent, null); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); @@ -308,7 +295,7 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, ]); - const actual = await testObject.merge(localContent, remoteContent, remoteContent); + const actual = mergeKeybindings(localContent, remoteContent, remoteContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); @@ -324,7 +311,7 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+d', command: '-a' }, { key: 'cmd+c', command: 'b', args: { text: '`' } }, ]); - const actual = await testObject.merge(localContent, remoteContent, remoteContent); + const actual = mergeKeybindings(localContent, remoteContent, remoteContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); @@ -338,7 +325,7 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, ]); - const actual = await testObject.merge(localContent, remoteContent, remoteContent); + const actual = mergeKeybindings(localContent, remoteContent, remoteContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); @@ -351,7 +338,7 @@ suite('KeybindingsMerge - No Conflicts', () => { const remoteContent = stringify([ { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, ]); - const actual = await testObject.merge(localContent, remoteContent, remoteContent); + const actual = mergeKeybindings(localContent, remoteContent, remoteContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); @@ -370,7 +357,7 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+d', command: 'b' }, { key: 'cmd+d', command: 'a' }, ]); - const actual = await testObject.merge(localContent, remoteContent, remoteContent); + const actual = mergeKeybindings(localContent, remoteContent, remoteContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); @@ -402,7 +389,7 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'cmd+d', command: 'c', when: 'context1' }, { key: 'cmd+c', command: '-c' }, ]); - const actual = await testObject.merge(localContent, remoteContent, remoteContent); + const actual = mergeKeybindings(localContent, remoteContent, remoteContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, expected); @@ -445,7 +432,7 @@ suite('KeybindingsMerge - No Conflicts', () => { ]); //'[\n\t{\n\t\t"key": "alt+d",\n\t\t"command": "-f"\n\t},\n\t{\n\t\t"key": "cmd+d",\n\t\t"command": "d"\n\t},\n\t{\n\t\t"key": "cmd+c",\n\t\t"command": "-c"\n\t},\n\t{\n\t\t"key": "cmd+d",\n\t\t"command": "c",\n\t\t"when": "context1"\n\t},\n\t{\n\t\t"key": "alt+a",\n\t\t"command": "f"\n\t},\n\t{\n\t\t"key": "alt+e",\n\t\t"command": "e"\n\t},\n\t{\n\t\t"key": "alt+g",\n\t\t"command": "g",\n\t\t"when": "context2"\n\t}\n]' //'[\n\t{\n\t\t"key": "alt+d",\n\t\t"command": "-f"\n\t},\n\t{\n\t\t"key": "cmd+d",\n\t\t"command": "d"\n\t},\n\t{\n\t\t"key": "cmd+c",\n\t\t"command": "-c"\n\t},\n\t{\n\t\t"key": "alt+c",\n\t\t"command": "c",\n\t\t"when": "context1"\n\t},\n\t{\n\t\t"key": "alt+a",\n\t\t"command": "f"\n\t},\n\t{\n\t\t"key": "alt+e",\n\t\t"command": "e"\n\t},\n\t{\n\t\t"key": "alt+g",\n\t\t"command": "g",\n\t\t"when": "context2"\n\t}\n]' - const actual = await testObject.merge(localContent, remoteContent, baseContent); + const actual = mergeKeybindings(localContent, remoteContent, baseContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, expected); @@ -458,7 +445,7 @@ suite('KeybindingsMerge - Conflicts', () => { test('merge when local and remote with one entry but different value', async () => { const localContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const actual = await testObject.merge(localContent, remoteContent, null); + const actual = mergeKeybindings(localContent, remoteContent, null); assert.ok(actual.hasChanges); assert.ok(actual.hasConflicts); assert.equal(actual.mergeContent, @@ -490,7 +477,7 @@ suite('KeybindingsMerge - Conflicts', () => { { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+a', command: '-a', when: 'editorTextFocus && !editorReadonly' } ]); - const actual = await testObject.merge(localContent, remoteContent, null); + const actual = mergeKeybindings(localContent, remoteContent, null); assert.ok(actual.hasChanges); assert.ok(actual.hasConflicts); assert.equal(actual.mergeContent, @@ -527,7 +514,7 @@ suite('KeybindingsMerge - Conflicts', () => { const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); const localContent = stringify([]); const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const actual = await testObject.merge(localContent, remoteContent, baseContent); + const actual = mergeKeybindings(localContent, remoteContent, baseContent); assert.ok(actual.hasChanges); assert.ok(actual.hasConflicts); assert.equal(actual.mergeContent, @@ -548,7 +535,7 @@ suite('KeybindingsMerge - Conflicts', () => { const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); const localContent = stringify([{ key: 'alt+b', command: 'b' }]); const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const actual = await testObject.merge(localContent, remoteContent, baseContent); + const actual = mergeKeybindings(localContent, remoteContent, baseContent); assert.ok(actual.hasChanges); assert.ok(actual.hasConflicts); assert.equal(actual.mergeContent, @@ -574,7 +561,7 @@ suite('KeybindingsMerge - Conflicts', () => { const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); const remoteContent = stringify([]); - const actual = await testObject.merge(localContent, remoteContent, baseContent); + const actual = mergeKeybindings(localContent, remoteContent, baseContent); assert.ok(actual.hasChanges); assert.ok(actual.hasConflicts); assert.equal(actual.mergeContent, @@ -595,7 +582,7 @@ suite('KeybindingsMerge - Conflicts', () => { const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); const remoteContent = stringify([{ key: 'alt+b', command: 'b' }]); - const actual = await testObject.merge(localContent, remoteContent, baseContent); + const actual = mergeKeybindings(localContent, remoteContent, baseContent); assert.ok(actual.hasChanges); assert.ok(actual.hasConflicts); assert.equal(actual.mergeContent, @@ -647,7 +634,7 @@ suite('KeybindingsMerge - Conflicts', () => { { key: 'alt+c', command: 'c', when: 'context1' }, { key: 'alt+g', command: 'g', when: 'context2' }, ]); - const actual = await testObject.merge(localContent, remoteContent, baseContent); + const actual = mergeKeybindings(localContent, remoteContent, baseContent); assert.ok(actual.hasChanges); assert.ok(actual.hasConflicts); assert.equal(actual.mergeContent, @@ -718,6 +705,17 @@ suite('KeybindingsMerge - Conflicts', () => { }); +function mergeKeybindings(localContent: string, remoteContent: string, baseContent: string | null) { + const local = parse(localContent); + const remote = parse(remoteContent); + const base = baseContent ? parse(baseContent) : null; + const keys: IStringDictionary = {}; + for (const keybinding of [...local, ...remote, ...(base || [])]) { + keys[keybinding.key] = keybinding.key; + } + return merge(localContent, remoteContent, baseContent, keys); +} + function stringify(value: any): any { return JSON.stringify(value, null, '\t'); } diff --git a/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts b/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts index 9a07beb2623..3c94e35f321 100644 --- a/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts +++ b/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts @@ -4,22 +4,22 @@ *--------------------------------------------------------------------------------------------*/ import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { ISettingsMergeService, IKeybindingsMergeService } from 'vs/platform/userDataSync/common/userDataSync'; +import { ISettingsMergeService, IUserKeybindingsResolverService } from 'vs/platform/userDataSync/common/userDataSync'; import { Registry } from 'vs/platform/registry/common/platform'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { SettingsMergeChannel } from 'vs/platform/userDataSync/common/settingsSyncIpc'; -import { KeybindingsMergeChannel } from 'vs/platform/userDataSync/common/keybindingsSyncIpc'; +import { UserKeybindingsResolverServiceChannel } from 'vs/platform/userDataSync/common/keybindingsSyncIpc'; class UserDataSyncServicesContribution implements IWorkbenchContribution { constructor( @ISettingsMergeService settingsMergeService: ISettingsMergeService, - @IKeybindingsMergeService keybindingsMergeService: IKeybindingsMergeService, + @IUserKeybindingsResolverService keybindingsMergeService: IUserKeybindingsResolverService, @ISharedProcessService sharedProcessService: ISharedProcessService, ) { sharedProcessService.registerChannel('settingsMerge', new SettingsMergeChannel(settingsMergeService)); - sharedProcessService.registerChannel('keybindingsMerge', new KeybindingsMergeChannel(keybindingsMergeService)); + sharedProcessService.registerChannel('userKeybindingsResolver', new UserKeybindingsResolverServiceChannel(keybindingsMergeService)); } } diff --git a/src/vs/workbench/services/keybinding/common/keybindingsMerge.ts b/src/vs/workbench/services/keybinding/common/keybindingsMerge.ts deleted file mode 100644 index 0f3da528a2b..00000000000 --- a/src/vs/workbench/services/keybinding/common/keybindingsMerge.ts +++ /dev/null @@ -1,392 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as objects from 'vs/base/common/objects'; -import { parse } from 'vs/base/common/json'; -import { values, keys } from 'vs/base/common/map'; -import { IUserFriendlyKeybinding, IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { firstIndex as findFirstIndex, equals } from 'vs/base/common/arrays'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import * as contentUtil from 'vs/platform/userDataSync/common/content'; -import { IKeybindingsMergeService } from 'vs/platform/userDataSync/common/userDataSync'; - -interface ICompareResult { - added: Set; - removed: Set; - updated: Set; -} - -interface IMergeResult { - hasLocalForwarded: boolean; - hasRemoteForwarded: boolean; - added: Set; - removed: Set; - updated: Set; - conflicts: Set; -} - -export class KeybindingsMergeService implements IKeybindingsMergeService { - - _serviceBrand: undefined; - - constructor( - @IKeybindingService private readonly keybindingsService: IKeybindingService - ) { } - - public async merge(localContent: string, remoteContent: string, baseContent: string | null): Promise<{ mergeContent: string, hasChanges: boolean, hasConflicts: boolean }> { - const local = parse(localContent); - const remote = parse(remoteContent); - const base = baseContent ? parse(baseContent) : null; - const normalizedKeys = this.getNormalizedKeys(local, remote, base); - - let keybindingsMergeResult = this.computeMergeResultByKeybinding(local, remote, base, normalizedKeys); - - if (!keybindingsMergeResult.hasLocalForwarded && !keybindingsMergeResult.hasRemoteForwarded) { - // No changes found between local and remote. - return { mergeContent: localContent, hasChanges: false, hasConflicts: false }; - } - - if (!keybindingsMergeResult.hasLocalForwarded && keybindingsMergeResult.hasRemoteForwarded) { - return { mergeContent: remoteContent, hasChanges: true, hasConflicts: false }; - } - - if (keybindingsMergeResult.hasLocalForwarded && !keybindingsMergeResult.hasRemoteForwarded) { - // Local has moved forward and remote has not. Return local. - return { mergeContent: localContent, hasChanges: true, hasConflicts: false }; - } - - // Both local and remote has moved forward. - const localByCommand = this.byCommand(local); - const remoteByCommand = this.byCommand(remote); - const baseByCommand = base ? this.byCommand(base) : null; - const localToRemoteByCommand = this.compareByCommand(localByCommand, remoteByCommand, normalizedKeys); - const baseToLocalByCommand = baseByCommand ? this.compareByCommand(baseByCommand, localByCommand, normalizedKeys) : { added: keys(localByCommand).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; - const baseToRemoteByCommand = baseByCommand ? this.compareByCommand(baseByCommand, remoteByCommand, normalizedKeys) : { added: keys(remoteByCommand).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; - - const commandsMergeResult = this.computeMergeResult(localToRemoteByCommand, baseToLocalByCommand, baseToRemoteByCommand); - const eol = contentUtil.getEol(localContent); - let mergeContent = localContent; - let mergeContentChanged = false; - - // Removed commands in Remote - for (const command of values(commandsMergeResult.removed)) { - if (commandsMergeResult.conflicts.has(command)) { - continue; - } - mergeContent = this.removeKeybindings(mergeContent, eol, command); - mergeContentChanged = true; - } - - if (mergeContentChanged) { - keybindingsMergeResult = this.computeMergeResultByKeybinding(local, remote, parse(mergeContent), normalizedKeys); - } - mergeContentChanged = false; - // Added commands in remote - for (const command of values(commandsMergeResult.added)) { - if (commandsMergeResult.conflicts.has(command)) { - continue; - } - const keybindings = remoteByCommand.get(command)!; - // Ignore negated commands - if (keybindings.some(keybinding => keybinding.command !== `-${command}` && keybindingsMergeResult.conflicts.has(normalizedKeys.get(keybinding.key)!))) { - commandsMergeResult.conflicts.add(command); - continue; - } - mergeContent = this.addKeybindings(mergeContent, eol, keybindings); - mergeContentChanged = true; - } - - if (mergeContentChanged) { - keybindingsMergeResult = this.computeMergeResultByKeybinding(local, remote, parse(mergeContent), normalizedKeys); - } - // Updated commands in Remote - for (const command of values(commandsMergeResult.updated)) { - if (commandsMergeResult.conflicts.has(command)) { - continue; - } - const keybindings = remoteByCommand.get(command)!; - // Ignore negated commands - if (keybindings.some(keybinding => keybinding.command !== `-${command}` && keybindingsMergeResult.conflicts.has(normalizedKeys.get(keybinding.key)!))) { - commandsMergeResult.conflicts.add(command); - continue; - } - mergeContent = this.updateKeybindings(mergeContent, eol, command, keybindings); - } - - const hasConflicts = commandsMergeResult.conflicts.size > 0; - if (hasConflicts) { - mergeContent = `<<<<<<< local${eol}` - + mergeContent - + `${eol}=======${eol}` - + remoteContent - + `${eol}>>>>>>> remote`; - } - - return { mergeContent, hasChanges: true, hasConflicts }; - } - - private computeMergeResult(localToRemote: ICompareResult, baseToLocal: ICompareResult, baseToRemote: ICompareResult): { added: Set, removed: Set, updated: Set, conflicts: Set } { - const added: Set = new Set(); - const removed: Set = new Set(); - const updated: Set = new Set(); - const conflicts: Set = new Set(); - - // Removed keys in Local - for (const key of values(baseToLocal.removed)) { - // Got updated in remote - if (baseToRemote.updated.has(key)) { - conflicts.add(key); - } - } - - // Removed keys in Remote - for (const key of values(baseToRemote.removed)) { - if (conflicts.has(key)) { - continue; - } - // Got updated in local - if (baseToLocal.updated.has(key)) { - conflicts.add(key); - } else { - // remove the key - removed.add(key); - } - } - - // Added keys in Local - for (const key of values(baseToLocal.added)) { - if (conflicts.has(key)) { - continue; - } - // Got added in remote - if (baseToRemote.added.has(key)) { - // Has different value - if (localToRemote.updated.has(key)) { - conflicts.add(key); - } - } - } - - // Added keys in remote - for (const key of values(baseToRemote.added)) { - if (conflicts.has(key)) { - continue; - } - // Got added in local - if (baseToLocal.added.has(key)) { - // Has different value - if (localToRemote.updated.has(key)) { - conflicts.add(key); - } - } else { - added.add(key); - } - } - - // Updated keys in Local - for (const key of values(baseToLocal.updated)) { - if (conflicts.has(key)) { - continue; - } - // Got updated in remote - if (baseToRemote.updated.has(key)) { - // Has different value - if (localToRemote.updated.has(key)) { - conflicts.add(key); - } - } - } - - // Updated keys in Remote - for (const key of values(baseToRemote.updated)) { - if (conflicts.has(key)) { - continue; - } - // Got updated in local - if (baseToLocal.updated.has(key)) { - // Has different value - if (localToRemote.updated.has(key)) { - conflicts.add(key); - } - } else { - // updated key - updated.add(key); - } - } - return { added, removed, updated, conflicts }; - } - - private getNormalizedKeys(local: IUserFriendlyKeybinding[], remote: IUserFriendlyKeybinding[], base: IUserFriendlyKeybinding[] | null): Map { - const keys = new Map(); - for (const keybinding of [...local, ...remote, ...(base || [])]) { - keys.set(keybinding.key, this.keybindingsService.resolveUserBinding(keybinding.key).map(part => part.getUserSettingsLabel()).join(' ')); - } - return keys; - } - - private computeMergeResultByKeybinding(local: IUserFriendlyKeybinding[], remote: IUserFriendlyKeybinding[], base: IUserFriendlyKeybinding[] | null, normalizedKeys: Map): IMergeResult { - const empty = new Set(); - const localByKeybinding = this.byKeybinding(local, normalizedKeys); - const remoteByKeybinding = this.byKeybinding(remote, normalizedKeys); - const baseByKeybinding = base ? this.byKeybinding(base, normalizedKeys) : null; - - const localToRemoteByKeybinding = this.compareByKeybinding(localByKeybinding, remoteByKeybinding); - if (localToRemoteByKeybinding.added.size === 0 && localToRemoteByKeybinding.removed.size === 0 && localToRemoteByKeybinding.updated.size === 0) { - return { hasLocalForwarded: false, hasRemoteForwarded: false, added: empty, removed: empty, updated: empty, conflicts: empty }; - } - - const baseToLocalByKeybinding = baseByKeybinding ? this.compareByKeybinding(baseByKeybinding, localByKeybinding) : { added: keys(localByKeybinding).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; - if (baseToLocalByKeybinding.added.size === 0 && baseToLocalByKeybinding.removed.size === 0 && baseToLocalByKeybinding.updated.size === 0) { - // Remote has moved forward and local has not. - return { hasLocalForwarded: false, hasRemoteForwarded: true, added: empty, removed: empty, updated: empty, conflicts: empty }; - } - - const baseToRemoteByKeybinding = baseByKeybinding ? this.compareByKeybinding(baseByKeybinding, remoteByKeybinding) : { added: keys(remoteByKeybinding).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; - if (baseToRemoteByKeybinding.added.size === 0 && baseToRemoteByKeybinding.removed.size === 0 && baseToRemoteByKeybinding.updated.size === 0) { - return { hasLocalForwarded: true, hasRemoteForwarded: false, added: empty, removed: empty, updated: empty, conflicts: empty }; - } - - const { added, removed, updated, conflicts } = this.computeMergeResult(localToRemoteByKeybinding, baseToLocalByKeybinding, baseToRemoteByKeybinding); - return { hasLocalForwarded: true, hasRemoteForwarded: true, added, removed, updated, conflicts }; - } - - private byKeybinding(keybindings: IUserFriendlyKeybinding[], keys: Map) { - const map: Map = new Map(); - for (const keybinding of keybindings) { - const key = keys.get(keybinding.key)!; - let value = map.get(key); - if (!value) { - value = []; - map.set(key, value); - } - value.push(keybinding); - - } - return map; - } - - private byCommand(keybindings: IUserFriendlyKeybinding[]): Map { - const map: Map = new Map(); - for (const keybinding of keybindings) { - const command = keybinding.command[0] === '-' ? keybinding.command.substring(1) : keybinding.command; - let value = map.get(command); - if (!value) { - value = []; - map.set(command, value); - } - value.push(keybinding); - } - return map; - } - - - private compareByKeybinding(from: Map, to: Map): ICompareResult { - const fromKeys = keys(from); - const toKeys = keys(to); - const added = toKeys.filter(key => fromKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); - const removed = fromKeys.filter(key => toKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); - const updated: Set = new Set(); - - for (const key of fromKeys) { - if (removed.has(key)) { - continue; - } - const value1: IUserFriendlyKeybinding[] = from.get(key)!.map(keybinding => ({ ...keybinding, ...{ key } })); - const value2: IUserFriendlyKeybinding[] = to.get(key)!.map(keybinding => ({ ...keybinding, ...{ key } })); - if (!equals(value1, value2, (a, b) => this.isSameKeybinding(a, b))) { - updated.add(key); - } - } - - return { added, removed, updated }; - } - - private compareByCommand(from: Map, to: Map, normalizedKeys: Map): ICompareResult { - const fromKeys = keys(from); - const toKeys = keys(to); - const added = toKeys.filter(key => fromKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); - const removed = fromKeys.filter(key => toKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); - const updated: Set = new Set(); - - for (const key of fromKeys) { - if (removed.has(key)) { - continue; - } - const value1: IUserFriendlyKeybinding[] = from.get(key)!.map(keybinding => ({ ...keybinding, ...{ key: normalizedKeys.get(keybinding.key)! } })); - const value2: IUserFriendlyKeybinding[] = to.get(key)!.map(keybinding => ({ ...keybinding, ...{ key: normalizedKeys.get(keybinding.key)! } })); - if (!this.areSameKeybindingsWithSameCommand(value1, value2)) { - updated.add(key); - } - } - - return { added, removed, updated }; - } - - private areSameKeybindingsWithSameCommand(value1: IUserFriendlyKeybinding[], value2: IUserFriendlyKeybinding[]): boolean { - // Compare entries adding keybindings - if (!equals(value1.filter(({ command }) => command[0] !== '-'), value2.filter(({ command }) => command[0] !== '-'), (a, b) => this.isSameKeybinding(a, b))) { - return false; - } - // Compare entries removing keybindings - if (!equals(value1.filter(({ command }) => command[0] === '-'), value2.filter(({ command }) => command[0] === '-'), (a, b) => this.isSameKeybinding(a, b))) { - return false; - } - return true; - } - - private isSameKeybinding(a: IUserFriendlyKeybinding, b: IUserFriendlyKeybinding): boolean { - if (a.command !== b.command) { - return false; - } - if (a.key !== b.key) { - return false; - } - const whenA = ContextKeyExpr.deserialize(a.when); - const whenB = ContextKeyExpr.deserialize(b.when); - if ((whenA && !whenB) || (!whenA && whenB)) { - return false; - } - if (whenA && whenB && !whenA.equals(whenB)) { - return false; - } - if (!objects.equals(a.args, b.args)) { - return false; - } - return true; - } - - private addKeybindings(content: string, eol: string, keybindings: IUserFriendlyKeybinding[]): string { - for (const keybinding of keybindings) { - content = contentUtil.edit(content, eol, [-1], keybinding); - } - return content; - } - - private removeKeybindings(content: string, eol: string, command: string): string { - const keybindings = parse(content); - for (let index = keybindings.length - 1; index >= 0; index--) { - if (keybindings[index].command === command || keybindings[index].command === `-${command}`) { - content = contentUtil.edit(content, eol, [index], undefined); - } - } - return content; - } - - private updateKeybindings(content: string, eol: string, command: string, keybindings: IUserFriendlyKeybinding[]): string { - const allKeybindings = parse(content); - const location = findFirstIndex(allKeybindings, keybinding => keybinding.command === command || keybinding.command === `-${command}`); - // Remove all entries with this command - for (let index = allKeybindings.length - 1; index >= 0; index--) { - if (allKeybindings[index].command === command || allKeybindings[index].command === `-${command}`) { - content = contentUtil.edit(content, eol, [index], undefined); - } - } - // add all entries at the same location where the entry with this command was located. - for (let index = keybindings.length - 1; index >= 0; index--) { - content = contentUtil.edit(content, eol, [location], keybindings[index]); - } - return content; - } -} diff --git a/src/vs/workbench/services/userDataSync/common/keybindingsMerge.ts b/src/vs/workbench/services/userDataSync/common/keybindingsMerge.ts new file mode 100644 index 00000000000..d263bfa18d5 --- /dev/null +++ b/src/vs/workbench/services/userDataSync/common/keybindingsMerge.ts @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { parse } from 'vs/base/common/json'; +import { IUserFriendlyKeybinding, IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IUserKeybindingsResolverService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IStringDictionary } from 'vs/base/common/collections'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; + +export class UserKeybindingsResolverService implements IUserKeybindingsResolverService { + + _serviceBrand: undefined; + + constructor( + @IKeybindingService private readonly keybindingsService: IKeybindingService + ) { } + + public async resolveUserKeybindings(localContent: string, remoteContent: string, baseContent: string | null): Promise> { + const local = parse(localContent); + const remote = parse(remoteContent); + const base = baseContent ? parse(baseContent) : null; + const keys: IStringDictionary = {}; + for (const keybinding of [...local, ...remote, ...(base || [])]) { + keys[keybinding.key] = this.keybindingsService.resolveUserBinding(keybinding.key).map(part => part.getUserSettingsLabel()).join(' '); + } + return keys; + } +} + +registerSingleton(IUserKeybindingsResolverService, UserKeybindingsResolverService); diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index ebc12c69d14..316b630b15a 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -80,7 +80,7 @@ import 'vs/workbench/services/extensionManagement/common/extensionEnablementServ import 'vs/workbench/services/notification/common/notificationService'; import 'vs/workbench/services/extensions/common/staticExtensions'; import 'vs/workbench/services/userDataSync/common/settingsMergeService'; -import 'vs/workbench/services/keybinding/common/keybindingsMerge'; +import 'vs/workbench/services/userDataSync/common/keybindingsMerge'; import 'vs/workbench/services/path/common/remotePathService'; import 'vs/workbench/services/remote/common/remoteExplorerService'; import 'vs/workbench/services/workingCopy/common/workingCopyService'; From 83c5becc4aba9ca8fef3771ec0e1fbde0db23d8a Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 28 Nov 2019 20:50:13 +0100 Subject: [PATCH 041/255] remove smart merging --- .../platform/userDataSync/common/keybindingsMerge.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/vs/platform/userDataSync/common/keybindingsMerge.ts b/src/vs/platform/userDataSync/common/keybindingsMerge.ts index 84c6a15062f..c41df367b34 100644 --- a/src/vs/platform/userDataSync/common/keybindingsMerge.ts +++ b/src/vs/platform/userDataSync/common/keybindingsMerge.ts @@ -59,7 +59,6 @@ export function merge(localContent: string, remoteContent: string, baseContent: const commandsMergeResult = computeMergeResult(localToRemoteByCommand, baseToLocalByCommand, baseToRemoteByCommand); const eol = contentUtil.getEol(localContent); let mergeContent = localContent; - let mergeContentChanged = false; // Removed commands in Remote for (const command of values(commandsMergeResult.removed)) { @@ -67,13 +66,8 @@ export function merge(localContent: string, remoteContent: string, baseContent: continue; } mergeContent = removeKeybindings(mergeContent, eol, command); - mergeContentChanged = true; } - if (mergeContentChanged) { - keybindingsMergeResult = computeMergeResultByKeybinding(local, remote, parse(mergeContent), normalizedKeys); - } - mergeContentChanged = false; // Added commands in remote for (const command of values(commandsMergeResult.added)) { if (commandsMergeResult.conflicts.has(command)) { @@ -86,12 +80,8 @@ export function merge(localContent: string, remoteContent: string, baseContent: continue; } mergeContent = addKeybindings(mergeContent, eol, keybindings); - mergeContentChanged = true; } - if (mergeContentChanged) { - keybindingsMergeResult = computeMergeResultByKeybinding(local, remote, parse(mergeContent), normalizedKeys); - } // Updated commands in Remote for (const command of values(commandsMergeResult.updated)) { if (commandsMergeResult.conflicts.has(command)) { From 17b53573ac30075394841eb6eb9f41b11e66c48d Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 28 Nov 2019 23:37:17 +0100 Subject: [PATCH 042/255] fix default keybindings content --- src/vs/platform/userDataSync/common/keybindingsSync.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index da6c85b1ab7..fa7a0f22ae5 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -210,7 +210,7 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser let previewContent = null; if (remoteContent) { - const localContent: string = fileContent ? fileContent.value.toString() : '{}'; + const localContent: string = fileContent ? fileContent.value.toString() : '[]'; if (this.hasErrors(localContent)) { this.logService.error('Keybindings: Unable to sync keybindings as there are errors/warning in keybindings file.'); return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts }; From f26416cd5ca82c38e17cd7abef54666002592760 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Sun, 1 Dec 2019 20:28:04 +0100 Subject: [PATCH 043/255] Support syncing keybindings per platform --- .../userDataSync/common/keybindingsSync.ts | 84 ++++++++++++++++--- .../userDataSync/common/userDataSync.ts | 6 ++ 2 files changed, 78 insertions(+), 12 deletions(-) diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index fa7a0f22ae5..20779f8687d 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -17,6 +17,15 @@ import { URI } from 'vs/base/common/uri'; import { joinPath } from 'vs/base/common/resources'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { OS, OperatingSystem } from 'vs/base/common/platform'; +import { isUndefined } from 'vs/base/common/types'; + +interface ISyncContent { + mac?: string; + linux?: string; + windows?: string; + all?: string; +} interface ISyncPreviewResult { readonly fileContent: IFileContent | null; @@ -54,6 +63,7 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser super(); this.lastSyncKeybindingsResource = joinPath(this.environmentService.userRoamingDataHome, '.lastSyncKeybindings.json'); this.throttledDelayer = this._register(new ThrottledDelayer(500)); + this._register(this.fileService.watch(this.environmentService.keybindingsResource)); this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.environmentService.keybindingsResource))(() => this.throttledDelayer.trigger(() => this.onDidChangeKeybindings()))); } @@ -166,12 +176,14 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser } if (hasRemoteChanged) { this.logService.info('Keybindings: Updating remote keybindings'); - const ref = await this.updateRemoteContent(content, remoteUserData.ref); - remoteUserData = { ref, content }; + const remoteContents = this.updateSyncContent(content, remoteUserData.content); + const ref = await this.updateRemoteUserData(remoteContents, remoteUserData.ref); + remoteUserData = { ref, content: remoteContents }; } if (remoteUserData.content) { this.logService.info('Keybindings: Updating last synchronised keybindings'); - await this.updateLastSyncUserData(remoteUserData); + const lastSyncContent = this.updateSyncContent(content, null); + await this.updateLastSyncUserData({ ref: remoteUserData.ref, content: lastSyncContent }); } // Delete the preview @@ -200,8 +212,9 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser private async generatePreview(token: CancellationToken): Promise { const lastSyncData = await this.getLastSyncUserData(); - const remoteUserData = await this.getRemoteContent(lastSyncData); - const remoteContent: string | null = remoteUserData.content; + const lastSyncContent = lastSyncData && lastSyncData.content ? this.getKeybindingsContentFromSyncContent(lastSyncData.content) : null; + const remoteUserData = await this.getRemoteUserData(lastSyncData); + const remoteContent = remoteUserData.content ? this.getKeybindingsContentFromSyncContent(remoteUserData.content) : null; // Get file content last to get the latest const fileContent = await this.getLocalContent(); let hasLocalChanged: boolean = false; @@ -216,13 +229,13 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts }; } - if (!lastSyncData // First time sync - || lastSyncData.content !== localContent // Local has forwarded - || lastSyncData.content !== remoteContent // Remote has forwarded + if (!lastSyncContent // First time sync + || lastSyncContent !== localContent // Local has forwarded + || lastSyncContent !== remoteContent // Remote has forwarded ) { this.logService.trace('Keybindings: Merging remote keybindings with local keybindings...'); - const keys = await this.userKeybindingsResolverService.resolveUserKeybindings(localContent, remoteContent, lastSyncData ? lastSyncData.content : null); - const result = merge(localContent, remoteContent, lastSyncData ? lastSyncData.content : null, keys); + const keys = await this.userKeybindingsResolverService.resolveUserKeybindings(localContent, remoteContent, lastSyncContent); + const result = merge(localContent, remoteContent, lastSyncContent, keys); // Sync only if there are changes if (result.hasChanges) { hasLocalChanged = result.mergeContent !== localContent; @@ -278,11 +291,58 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser await this.fileService.writeFile(this.lastSyncKeybindingsResource, VSBuffer.fromString(JSON.stringify(remoteUserData))); } - private async getRemoteContent(lastSyncData: IUserData | null): Promise { + private async getRemoteUserData(lastSyncData: IUserData | null): Promise { return this.userDataSyncStoreService.read(KeybindingsSynchroniser.EXTERNAL_USER_DATA_KEYBINDINGS_KEY, lastSyncData); } - private async updateRemoteContent(content: string, ref: string | null): Promise { + private async updateRemoteUserData(content: string, ref: string | null): Promise { return this.userDataSyncStoreService.write(KeybindingsSynchroniser.EXTERNAL_USER_DATA_KEYBINDINGS_KEY, content, ref); } + + private getKeybindingsContentFromSyncContent(syncContent: string): string | null { + try { + const parsed = JSON.parse(syncContent); + if (!this.configurationService.getValue('sync.keybindingsPerPlatform')) { + return isUndefined(parsed.all) ? null : parsed.all; + } + switch (OS) { + case OperatingSystem.Macintosh: + return isUndefined(parsed.mac) ? null : parsed.mac; + case OperatingSystem.Linux: + return isUndefined(parsed.linux) ? null : parsed.linux; + case OperatingSystem.Windows: + return isUndefined(parsed.windows) ? null : parsed.windows; + } + } catch (e) { + this.logService.error(e); + return null; + } + } + + private updateSyncContent(keybindingsContent: string, syncContent: string | null): string { + let parsed: ISyncContent = {}; + try { + parsed = JSON.parse(syncContent || '{}'); + } catch (e) { + this.logService.error(e); + } + if (!this.configurationService.getValue('sync.keybindingsPerPlatform')) { + parsed.all = keybindingsContent; + } else { + delete parsed.all; + } + switch (OS) { + case OperatingSystem.Macintosh: + parsed.mac = keybindingsContent; + break; + case OperatingSystem.Linux: + parsed.linux = keybindingsContent; + break; + case OperatingSystem.Windows: + parsed.windows = keybindingsContent; + break; + } + return JSON.stringify(parsed); + } + } diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index 660cae2d320..a809bdf74ca 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -59,6 +59,12 @@ export function registerConfiguration(): IDisposable { default: true, scope: ConfigurationScope.APPLICATION, }, + 'sync.keybindingsPerPlatform': { + type: 'boolean', + description: localize('sync.keybindingsPerPlatform', "Synchronize keybindings per platform."), + default: true, + scope: ConfigurationScope.APPLICATION, + }, 'sync.ignoredExtensions': { 'type': 'array', description: localize('sync.ignoredExtensions', "Configure extensions to be ignored while synchronizing."), From 72873267cbd673d4a1487f4bef87f9dd0739827c Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Sun, 1 Dec 2019 20:57:37 +0100 Subject: [PATCH 044/255] revert change --- .../keybinding/test/electron-browser/keybindingEditing.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts index 8b62fa9c23f..26baa78b0ab 100644 --- a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts +++ b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts @@ -68,7 +68,7 @@ interface Modifiers { shiftKey?: boolean; } -suite('KeybindingsEditing', () => { +suite.skip('KeybindingsEditing', () => { let instantiationService: TestInstantiationService; let testObject: KeybindingsEditingService; From b7b719bcac2d004a1ec00644f0f503e7f32c6419 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Sun, 1 Dec 2019 20:59:17 +0100 Subject: [PATCH 045/255] fix compilation --- src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index cda422c22b8..eddeeff725f 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -398,7 +398,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo command: { id: continueSyncCommandId, title: localize('continue sync', "Sync: Continue"), - iconLocation: { + icon: { light: SYNC_PUSH_LIGHT_ICON_URI, dark: SYNC_PUSH_DARK_ICON_URI } From 709e1732aab848d6490321a86b9a50471fec23b0 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sun, 1 Dec 2019 12:29:11 -0800 Subject: [PATCH 046/255] Delete unneeded vscode-ripgrep.d.ts #83421 --- src/typings/vscode-ripgrep.d.ts | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 src/typings/vscode-ripgrep.d.ts diff --git a/src/typings/vscode-ripgrep.d.ts b/src/typings/vscode-ripgrep.d.ts deleted file mode 100644 index 4c5c89c3ca8..00000000000 --- a/src/typings/vscode-ripgrep.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -declare module 'vscode-ripgrep' { - export const rgPath: string; -} From 480076256ffd97ce6c3753b95c970f5e45683402 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sun, 1 Dec 2019 12:50:37 -0800 Subject: [PATCH 047/255] Fix setting deprecation message overflowing other rows Fix #84007 --- .../preferences/browser/media/settingsEditor2.css | 9 +++++---- .../contrib/preferences/browser/settingsTree.ts | 5 ++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css index 894f925ba71..ec040450cce 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css +++ b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css @@ -346,6 +346,11 @@ margin-top: 3px; user-select: text; -webkit-user-select: text; + display: none; +} + +.settings-editor > .settings-body > .settings-tree-container .setting-item-contents.is-deprecated .setting-item-deprecation-message { + display: block; } .settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description { @@ -354,10 +359,6 @@ -webkit-user-select: text; } -.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-deprecation-message { - position: absolute; -} - .settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-validation-message { display: none; } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index d52e1403ed8..f953fb205e6 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -467,7 +467,10 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre } const onChange = (value: any) => this._onDidChangeSetting.fire({ key: element.setting.key, value, type: template.context!.valueType }); - template.deprecationWarningElement.innerText = element.setting.deprecationMessage || ''; + const deprecationText = element.setting.deprecationMessage || ''; + template.deprecationWarningElement.innerText = deprecationText; + DOM.toggleClass(template.containerElement, 'is-deprecated', !!deprecationText); + this.renderValue(element, template, onChange); } From 789416f4a7f5fa42acbeb9d4df9907255d1d0f80 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sun, 1 Dec 2019 20:52:32 +0000 Subject: [PATCH 048/255] Fix subpixel AA for search viewlet message Fix #84007 --- .../contrib/search/browser/media/searchview.css | 4 ---- src/vs/workbench/contrib/search/browser/searchView.ts | 9 ++++++++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/media/searchview.css b/src/vs/workbench/contrib/search/browser/media/searchview.css index 524b571529c..36fb83e2602 100644 --- a/src/vs/workbench/contrib/search/browser/media/searchview.css +++ b/src/vs/workbench/contrib/search/browser/media/searchview.css @@ -302,10 +302,6 @@ background-color: rgba(255, 255, 255, 0.1) !important; } -.vs-dark .search-view .message { - opacity: .5; -} - .vs-dark .search-view .foldermatch, .vs-dark .search-view .filematch { padding: 0; diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index c814ae3a86b..419611a9462 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -36,7 +36,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IProgressService, IProgressStep, IProgress } from 'vs/platform/progress/common/progress'; import { IPatternInfo, ISearchComplete, ISearchConfiguration, ISearchConfigurationProperties, ITextQuery, VIEW_ID, VIEWLET_ID } from 'vs/workbench/services/search/common/search'; import { ISearchHistoryService, ISearchHistoryValues } from 'vs/workbench/contrib/search/common/searchHistoryService'; -import { diffInserted, diffInsertedOutline, diffRemoved, diffRemovedOutline, editorFindMatchHighlight, editorFindMatchHighlightBorder, listActiveSelectionForeground } from 'vs/platform/theme/common/colorRegistry'; +import { diffInserted, diffInsertedOutline, diffRemoved, diffRemovedOutline, editorFindMatchHighlight, editorFindMatchHighlightBorder, listActiveSelectionForeground, foreground } from 'vs/platform/theme/common/colorRegistry'; import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { OpenFileFolderAction, OpenFolderAction } from 'vs/workbench/browser/actions/workspaceActions'; @@ -66,6 +66,7 @@ import { Selection } from 'vs/editor/common/core/selection'; import { SIDE_BAR_BACKGROUND, PANEL_BACKGROUND } from 'vs/workbench/common/theme'; import { createEditorFromSearchResult } from 'vs/workbench/contrib/search/browser/searchEditor'; import { ILabelService } from 'vs/platform/label/common/label'; +import { Color, RGBA } from 'vs/base/common/color'; const $ = dom.$; @@ -1796,4 +1797,10 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { if (outlineSelectionColor) { collector.addRule(`.monaco-workbench .search-view .monaco-list.element-focused .monaco-list-row.focused.selected:not(.highlighted) .action-label:focus { outline-color: ${outlineSelectionColor} }`); } + + const foregroundColor = theme.getColor(foreground); + if (foregroundColor) { + const fgWithOpacity = new Color(new RGBA(foregroundColor.rgba.r, foregroundColor.rgba.g, foregroundColor.rgba.b, 0.5)); + collector.addRule(`.vs-dark .search-view .message { color: ${fgWithOpacity}; }`); + } }); From eeb70c241c50d8043240cea6ccc36241e61eb02e Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Sun, 1 Dec 2019 23:12:17 +0100 Subject: [PATCH 049/255] skip tests --- .../platform/userDataSync/test/common/keybindingsMerge.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts b/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts index e3ba6d50ee7..536dae468ea 100644 --- a/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts +++ b/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts @@ -9,7 +9,7 @@ import { IStringDictionary } from 'vs/base/common/collections'; import { IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding'; import { parse } from 'vs/base/common/json'; -suite('KeybindingsMerge - No Conflicts', () => { +suite.skip('KeybindingsMerge - No Conflicts', () => { test('merge when local and remote are same with one entry', async () => { const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); From 9d555b1729329aac6a5fe1235d316fa47b217c84 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Sun, 1 Dec 2019 23:32:10 +0100 Subject: [PATCH 050/255] #85893 skip merge conflict tests --- .../userDataSync/test/common/keybindingsMerge.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts b/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts index 536dae468ea..4d81421ed62 100644 --- a/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts +++ b/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts @@ -9,7 +9,7 @@ import { IStringDictionary } from 'vs/base/common/collections'; import { IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding'; import { parse } from 'vs/base/common/json'; -suite.skip('KeybindingsMerge - No Conflicts', () => { +suite('KeybindingsMerge - No Conflicts', () => { test('merge when local and remote are same with one entry', async () => { const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); @@ -440,7 +440,7 @@ suite.skip('KeybindingsMerge - No Conflicts', () => { }); -suite('KeybindingsMerge - Conflicts', () => { +suite.skip('KeybindingsMerge - Conflicts', () => { test('merge when local and remote with one entry but different value', async () => { const localContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); From 7cf4cca47aa025a590fc939af54932042302be63 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sun, 1 Dec 2019 16:01:55 -0800 Subject: [PATCH 051/255] Fix slower search when query has a slash Fix #76871 --- src/vs/workbench/services/search/node/rawSearchService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/services/search/node/rawSearchService.ts b/src/vs/workbench/services/search/node/rawSearchService.ts index 04fb0cf5e1f..ac159a60c92 100644 --- a/src/vs/workbench/services/search/node/rawSearchService.ts +++ b/src/vs/workbench/services/search/node/rawSearchService.ts @@ -277,7 +277,7 @@ export class SearchService implements IRawSearchService { for (const previousSearch in cache.resultsToSearchCache) { // If we narrow down, we might be able to reuse the cached results if (strings.startsWith(searchValue, previousSearch)) { - if (hasPathSep && previousSearch.indexOf(sep) < 0) { + if (hasPathSep && previousSearch.indexOf(sep) < 0 && previousSearch !== '') { continue; // since a path character widens the search for potential more matches, require it in previous search too } From 43be40c388033443c5e98c387d1af8690dff4dc2 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 2 Dec 2019 09:48:55 +0100 Subject: [PATCH 052/255] Adopt extension kind for those extensions defined as ui extensions. For #85819 --- extensions/vscode-test-resolver/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/vscode-test-resolver/package.json b/extensions/vscode-test-resolver/package.json index a458e14185d..90e3e0e87ee 100644 --- a/extensions/vscode-test-resolver/package.json +++ b/extensions/vscode-test-resolver/package.json @@ -8,7 +8,7 @@ "engines": { "vscode": "^1.25.0" }, - "extensionKind": "ui", + "extensionKind": [ "ui" ], "scripts": { "compile": "node ./node_modules/vscode/bin/compile -watch -p ./", "vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:vscode-test-resolver ./tsconfig.json" From 5fc31f99265dcbe2f840752509aa04a9e2ded03e Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 2 Dec 2019 10:17:51 +0100 Subject: [PATCH 053/255] Fix #85404 --- .../parts/activitybar/activitybarPart.ts | 65 +++++++++++++++++-- .../contrib/update/browser/update.ts | 4 +- .../userDataSync/browser/userDataSync.ts | 5 +- 3 files changed, 65 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index b4837c8d638..29ac27a90b6 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -11,7 +11,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { Part } from 'vs/workbench/browser/part'; import { GlobalActivityActionViewItem, ViewletActivityAction, ToggleViewletAction, PlaceHolderToggleCompositePinnedAction, PlaceHolderViewletActivityAction } from 'vs/workbench/browser/parts/activitybar/activitybarActions'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { IBadge } from 'vs/workbench/services/activity/common/activity'; +import { IBadge, NumberBadge } from 'vs/workbench/services/activity/common/activity'; import { IWorkbenchLayoutService, Parts, Position as SideBarPosition } from 'vs/workbench/services/layout/browser/layoutService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IDisposable, toDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle'; @@ -24,7 +24,7 @@ import { Dimension, addClass, removeNode } from 'vs/base/browser/dom'; import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { ToggleCompositePinnedAction, ICompositeBarColors, ActivityAction } from 'vs/workbench/browser/parts/compositeBarActions'; +import { ToggleCompositePinnedAction, ICompositeBarColors, ActivityAction, ICompositeActivity } from 'vs/workbench/browser/parts/compositeBarActions'; import { ViewletDescriptor } from 'vs/workbench/browser/viewlet'; import { IViewsService, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainer, TEST_VIEW_CONTAINER_ID, IViewDescriptorCollection } from 'vs/workbench/common/views'; import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -68,6 +68,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { private globalActivityAction: ActivityAction | undefined; private globalActivityActionBar: ActionBar | undefined; + private globalActivity: ICompositeActivity[] = []; private customMenubar: CustomMenubarControl | undefined; private menubar: HTMLElement | undefined; @@ -202,18 +203,68 @@ export class ActivitybarPart extends Part implements IActivityBarService { } if (viewletOrActionId === GLOBAL_ACTIVITY_ID) { - return this.showGlobalActivity(badge, clazz); + return this.showGlobalActivity(badge, clazz, priority); } return Disposable.None; } - private showGlobalActivity(badge: IBadge, clazz?: string): IDisposable { + private showGlobalActivity(badge: IBadge, clazz?: string, priority?: number): IDisposable { + if (typeof priority !== 'number') { + priority = 0; + } + const activity: ICompositeActivity = { badge, clazz, priority }; + + for (let i = 0; i <= this.globalActivity.length; i++) { + if (i === this.globalActivity.length) { + this.globalActivity.push(activity); + break; + } else if (this.globalActivity[i].priority <= priority) { + this.globalActivity.splice(i, 0, activity); + break; + } + } + this.updateGlobalActivity(); + + return toDisposable(() => this.removeGlobalActivity(activity)); + } + + private removeGlobalActivity(activity: ICompositeActivity): void { + const index = this.globalActivity.indexOf(activity); + if (index !== -1) { + this.globalActivity.splice(index, 1); + this.updateGlobalActivity(); + } + } + + private updateGlobalActivity(): void { const globalActivityAction = assertIsDefined(this.globalActivityAction); + if (this.globalActivity.length) { + const [{ badge, clazz, priority }] = this.globalActivity; + if (badge instanceof NumberBadge && this.globalActivity.length > 1) { + const cumulativeNumberBadge = this.getCumulativeNumberBadge(priority); + globalActivityAction.setBadge(cumulativeNumberBadge); + } else { + globalActivityAction.setBadge(badge, clazz); + } + } else { + globalActivityAction.setBadge(undefined); + } + } - globalActivityAction.setBadge(badge, clazz); - - return toDisposable(() => globalActivityAction.setBadge(undefined)); + private getCumulativeNumberBadge(priority: number): NumberBadge { + const numberActivities = this.globalActivity.filter(activity => activity.badge instanceof NumberBadge && activity.priority === priority); + let number = numberActivities.reduce((result, activity) => { return result + (activity.badge).number; }, 0); + let descriptorFn = (): string => { + return numberActivities.reduce((result, activity, index) => { + result = result + (activity.badge).getDescription(); + if (index < numberActivities.length - 1) { + result = result + '\n'; + } + return result; + }, ''); + }; + return new NumberBadge(number, descriptorFn); } private uninstallMenubar() { diff --git a/src/vs/workbench/contrib/update/browser/update.ts b/src/vs/workbench/contrib/update/browser/update.ts index 4f5f9d2e278..cbadd30426a 100644 --- a/src/vs/workbench/contrib/update/browser/update.ts +++ b/src/vs/workbench/contrib/update/browser/update.ts @@ -238,18 +238,20 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu let badge: IBadge | undefined = undefined; let clazz: string | undefined; + let priority: number | undefined = undefined; if (state.type === StateType.AvailableForDownload || state.type === StateType.Downloaded || state.type === StateType.Ready) { badge = new NumberBadge(1, () => nls.localize('updateIsReady', "New {0} update available.", this.productService.nameShort)); } else if (state.type === StateType.CheckingForUpdates || state.type === StateType.Downloading || state.type === StateType.Updating) { badge = new ProgressBadge(() => nls.localize('updateIsReady', "New {0} update available.", this.productService.nameShort)); clazz = 'progress-badge'; + priority = 1; } this.badgeDisposable.clear(); if (badge) { - this.badgeDisposable.value = this.activityService.showActivity(GLOBAL_ACTIVITY_ID, badge, clazz); + this.badgeDisposable.value = this.activityService.showActivity(GLOBAL_ACTIVITY_ID, badge, clazz, priority); } this.state = state; diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index eddeeff725f..1f5577d1189 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -141,21 +141,24 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo let badge: IBadge | undefined = undefined; let clazz: string | undefined; + let priority: number | undefined = undefined; if (this.userDataSyncService.status !== SyncStatus.Uninitialized && this.configurationService.getValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING) && this.authTokenService.status === AuthTokenStatus.SignedOut) { badge = new NumberBadge(1, () => localize('sign in', "Sync: Sign in...")); } else if (this.authTokenService.status === AuthTokenStatus.SigningIn) { badge = new ProgressBadge(() => localize('signing in', "Signin in...")); clazz = 'progress-badge'; + priority = 1; } else if (this.userDataSyncService.status === SyncStatus.HasConflicts) { badge = new NumberBadge(1, () => localize('resolve conflicts', "Resolve Conflicts")); } else if (this.userDataSyncService.status === SyncStatus.Syncing) { badge = new ProgressBadge(() => localize('syncing', "Synchronizing User Configuration...")); clazz = 'progress-badge'; + priority = 1; } if (badge) { - this.badgeDisposable.value = this.activityService.showActivity(GLOBAL_ACTIVITY_ID, badge, clazz); + this.badgeDisposable.value = this.activityService.showActivity(GLOBAL_ACTIVITY_ID, badge, clazz, priority); } } From dc4c7922cc3dc8a354b6a7b7ebe687ec7d98d026 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 2 Dec 2019 10:21:59 +0100 Subject: [PATCH 054/255] fix check for config keys, #84678 --- src/vs/workbench/contrib/outline/browser/outlinePane.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/outline/browser/outlinePane.ts b/src/vs/workbench/contrib/outline/browser/outlinePane.ts index ec276fec9c0..9aa98e2743c 100644 --- a/src/vs/workbench/contrib/outline/browser/outlinePane.ts +++ b/src/vs/workbench/contrib/outline/browser/outlinePane.ts @@ -374,7 +374,7 @@ export class OutlinePane extends ViewletPane { } // This is a temporary solution to try and minimize refilters while // ConfigurationChangeEvents only provide the first section of the config path. - if (e.affectedKeys.some(key => key.search(/(outline|\[\w+\])/))) { + if (e.affectedKeys.some(key => key.search(/(outline|\[\w+\])/) === 0)) { this._tree.refilter(); } })); From 880c488b392fe6c365748d2ffa57c45a3d7002a4 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 2 Dec 2019 10:34:27 +0100 Subject: [PATCH 055/255] Fix #85543 --- .../services/userDataSync/common/settingsMergeService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/services/userDataSync/common/settingsMergeService.ts b/src/vs/workbench/services/userDataSync/common/settingsMergeService.ts index bf2eb9e6dc0..3e3c652ff1f 100644 --- a/src/vs/workbench/services/userDataSync/common/settingsMergeService.ts +++ b/src/vs/workbench/services/userDataSync/common/settingsMergeService.ts @@ -166,8 +166,8 @@ class SettingsMergeService implements ISettingsMergeService { } private editSetting(model: ITextModel, key: string, value: any | undefined): void { - const insertSpaces = false; - const tabSize = 4; + const insertSpaces = model.getOptions().insertSpaces; + const tabSize = model.getOptions().tabSize; const eol = model.getEOL(); const edit = setProperty(model.getValue(), [key], value, { tabSize, insertSpaces, eol })[0]; if (edit) { From 9995919f55541536eecb0f8d6fa426edfb085479 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Mon, 2 Dec 2019 10:34:45 +0100 Subject: [PATCH 056/255] Makes padding on custom tree mesage match search message Fixes #85771 --- src/vs/workbench/browser/parts/views/media/views.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/views/media/views.css b/src/vs/workbench/browser/parts/views/media/views.css index 16e966ce337..fb2b337fc50 100644 --- a/src/vs/workbench/browser/parts/views/media/views.css +++ b/src/vs/workbench/browser/parts/views/media/views.css @@ -44,7 +44,7 @@ .monaco-workbench .tree-explorer-viewlet-tree-view .message { display: flex; - padding: 4px 12px 0px 18px; + padding: 4px 12px 4px 18px; user-select: text; -webkit-user-select: text; } From d6dae16f40614cacde62c70c7c465ac0f7b49cb0 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 2 Dec 2019 10:31:54 +0100 Subject: [PATCH 057/255] Remove areas from the semantic tokens API --- .../src/colorizerTestMain.ts | 31 +- src/vs/editor/common/modes.ts | 34 +- .../common/services/modelServiceImpl.ts | 254 ++++++++--- src/vs/monaco.d.ts | 31 +- src/vs/vscode.proposed.d.ts | 83 ++-- .../api/browser/mainThreadLanguageFeatures.ts | 75 +--- .../workbench/api/common/extHost.api.impl.ts | 12 +- .../workbench/api/common/extHost.protocol.ts | 6 +- .../api/common/extHostLanguageFeatures.ts | 415 ++++-------------- src/vs/workbench/api/common/extHostTypes.ts | 73 ++- .../api/common/shared/semanticTokens.ts | 173 ++++---- .../api/semanticTokens.test.ts | 343 --------------- 12 files changed, 544 insertions(+), 986 deletions(-) delete mode 100644 src/vs/workbench/test/electron-browser/api/semanticTokens.test.ts diff --git a/extensions/vscode-colorize-tests/src/colorizerTestMain.ts b/extensions/vscode-colorize-tests/src/colorizerTestMain.ts index b8d0a5c146f..76c1277f2ac 100644 --- a/extensions/vscode-colorize-tests/src/colorizerTestMain.ts +++ b/extensions/vscode-colorize-tests/src/colorizerTestMain.ts @@ -10,34 +10,19 @@ export function activate(context: vscode.ExtensionContext): any { const tokenModifiers = ['static', 'abstract', 'deprecated']; const tokenTypes = ['strings', 'types', 'structs', 'classes', 'functions', 'variables']; - const legend = new vscode.SemanticColoringLegend(tokenTypes, tokenModifiers); + const legend = new vscode.SemanticTokensLegend(tokenTypes, tokenModifiers); - /* - * A certain token (at index `i` is encoded using 5 uint32 integers): - * - at index `5*i` - `deltaLine`: token line number, relative to `SemanticColoringArea.line` - * - at index `5*i+1` - `startCharacter`: token start character offset inside the line (inclusive) - * - at index `5*i+2` - `endCharacter`: token end character offset inside the line (exclusive) - * - at index `5*i+3` - `tokenType`: will be looked up in `SemanticColoringLegend.tokenTypes` - * - at index `5*i+4` - `tokenModifiers`: each set bit will be looked up in `SemanticColoringLegend.tokenModifiers` - */ - - const semanticHighlightProvider: vscode.SemanticColoringProvider = { - provideSemanticColoring(document: vscode.TextDocument): vscode.ProviderResult { - const result: number[] = []; + const semanticHighlightProvider: vscode.SemanticTokensProvider = { + provideSemanticTokens(document: vscode.TextDocument): vscode.ProviderResult { + const builder = new vscode.SemanticTokensBuilder(); const visitor: jsoncParser.JSONVisitor = { onObjectProperty: (property: string, _offset: number, length: number, startLine: number, startCharacter: number) => { - result.push(startLine); - result.push(startCharacter); - result.push(startCharacter + length); - - const [type, ...modifiers] = property.split('.'); let tokenType = legend.tokenTypes.indexOf(type); if (tokenType === -1) { tokenType = 0; } - result.push(tokenType); let tokenModifiers = 0; for (let i = 0; i < modifiers.length; i++) { @@ -46,15 +31,17 @@ export function activate(context: vscode.ExtensionContext): any { tokenModifiers = tokenModifiers | 1 << index; } } - result.push(tokenModifiers); + + builder.push(startLine, startCharacter, length, tokenType, tokenModifiers); } }; jsoncParser.visit(document.getText(), visitor); - return new vscode.SemanticColoring([new vscode.SemanticColoringArea(0, new Uint32Array(result))]); + + return new vscode.SemanticTokens(builder.build()); } }; - context.subscriptions.push(vscode.languages.registerSemanticColoringProvider({ pattern: '**/color-test.json' }, semanticHighlightProvider, legend)); + context.subscriptions.push(vscode.languages.registerSemanticTokensProvider({ pattern: '**/color-test.json' }, semanticHighlightProvider, legend)); } diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 5d3f0109194..3a1399ee682 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -1465,31 +1465,31 @@ export interface CodeLensProvider { resolveCodeLens?(model: model.ITextModel, codeLens: CodeLens, token: CancellationToken): ProviderResult; } -export interface SemanticColoringLegend { +export interface SemanticTokensLegend { readonly tokenTypes: string[]; readonly tokenModifiers: string[]; } -export interface SemanticColoringArea { - /** - * The zero-based line value where this token block begins. - */ - readonly line: number; - /** - * The actual token block encoded data. - */ +export interface SemanticTokens { + readonly resultId?: string; readonly data: Uint32Array; - } -export interface SemanticColoring { - readonly areas: SemanticColoringArea[]; - dispose(): void; +export interface SemanticTokensEdit { + readonly start: number; + readonly deleteCount: number; + readonly data?: Uint32Array; } -export interface SemanticColoringProvider { - getLegend(): SemanticColoringLegend; - provideSemanticColoring(model: model.ITextModel, token: CancellationToken): ProviderResult; +export interface SemanticTokensEdits { + readonly resultId?: string; + readonly edits: SemanticTokensEdit[]; +} + +export interface SemanticTokensProvider { + getLegend(): SemanticTokensLegend; + provideSemanticTokens(model: model.ITextModel, lastResultId: string | null, ranges: Range[] | null, token: CancellationToken): ProviderResult; + releaseSemanticTokens(resultId: string | undefined): void; } // --- feature registries ------ @@ -1597,7 +1597,7 @@ export const FoldingRangeProviderRegistry = new LanguageFeatureRegistry(); +export const SemanticTokensProviderRegistry = new LanguageFeatureRegistry(); /** * @internal diff --git a/src/vs/editor/common/services/modelServiceImpl.ts b/src/vs/editor/common/services/modelServiceImpl.ts index 3b9734e0a16..6d309ab6aa5 100644 --- a/src/vs/editor/common/services/modelServiceImpl.ts +++ b/src/vs/editor/common/services/modelServiceImpl.ts @@ -14,7 +14,7 @@ import { Range } from 'vs/editor/common/core/range'; import { DefaultEndOfLine, EndOfLinePreference, EndOfLineSequence, IIdentifiedSingleEditOperation, ITextBuffer, ITextBufferFactory, ITextModel, ITextModelCreationOptions } from 'vs/editor/common/model'; import { TextModel, createTextBuffer } from 'vs/editor/common/model/textModel'; import { IModelLanguageChangedEvent, IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents'; -import { LanguageIdentifier, SemanticColoringProviderRegistry, SemanticColoringProvider, SemanticColoring, SemanticColoringLegend } from 'vs/editor/common/modes'; +import { LanguageIdentifier, SemanticTokensProviderRegistry, SemanticTokensProvider, SemanticTokensLegend, SemanticTokens, SemanticTokensEdits } from 'vs/editor/common/modes'; import { PLAINTEXT_LANGUAGE_IDENTIFIER } from 'vs/editor/common/modes/modesRegistry'; import { ILanguageSelection } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; @@ -459,22 +459,22 @@ class SemanticColoringFeature extends Disposable { class SemanticStyling extends Disposable { - private _caches: WeakMap; + private _caches: WeakMap; constructor( private readonly _themeService: IThemeService ) { super(); - this._caches = new WeakMap(); + this._caches = new WeakMap(); if (this._themeService) { // workaround for tests which use undefined... :/ this._register(this._themeService.onThemeChange(() => { - this._caches = new WeakMap(); + this._caches = new WeakMap(); })); } } - public get(provider: SemanticColoringProvider): SemanticColoringProviderStyling { + public get(provider: SemanticTokensProvider): SemanticColoringProviderStyling { if (!this._caches.has(provider)) { this._caches.set(provider, new SemanticColoringProviderStyling(provider.getLegend(), this._themeService)); } @@ -580,7 +580,7 @@ class SemanticColoringProviderStyling { private readonly _hashTable: HashTable; constructor( - private readonly _legend: SemanticColoringLegend, + private readonly _legend: SemanticTokensLegend, private readonly _themeService: IThemeService ) { this._hashTable = new HashTable(); @@ -611,13 +611,39 @@ class SemanticColoringProviderStyling { } } +const enum SemanticColoringConstants { + /** + * Let's aim at having 8KB buffers if possible... + * So that would be 8192 / (5 * 4) = 409.6 tokens per area + */ + DesiredTokensPerArea = 400, + + /** + * Try to keep the total number of areas under 1024 if possible, + * simply compensate by having more tokens per area... + */ + DesiredMaxAreas = 1024, +} + +class SemanticTokensResponse { + constructor( + private readonly _provider: SemanticTokensProvider, + public readonly resultId: string | undefined, + public readonly data: Uint32Array + ) { } + + public dispose(): void { + this._provider.releaseSemanticTokens(this.resultId); + } +} + class ModelSemanticColoring extends Disposable { private _isDisposed: boolean; private readonly _model: ITextModel; private readonly _semanticStyling: SemanticStyling; private readonly _fetchSemanticTokens: RunOnceScheduler; - private _currentResponse: SemanticColoring | null; + private _currentResponse: SemanticTokensResponse | null; private _currentRequestCancellationTokenSource: CancellationTokenSource | null; constructor(model: ITextModel, themeService: IThemeService, stylingProvider: SemanticStyling) { @@ -631,12 +657,12 @@ class ModelSemanticColoring extends Disposable { this._currentRequestCancellationTokenSource = null; this._register(this._model.onDidChangeContent(e => this._fetchSemanticTokens.schedule())); - this._register(SemanticColoringProviderRegistry.onDidChange(e => this._fetchSemanticTokens.schedule())); + this._register(SemanticTokensProviderRegistry.onDidChange(e => this._fetchSemanticTokens.schedule())); if (themeService) { // workaround for tests which use undefined... :/ this._register(themeService.onThemeChange(_ => { // clear out existing tokens - this._setSemanticTokens(null, null, []); + this._setSemanticTokens(null, null, null, []); this._fetchSemanticTokens.schedule(); })); } @@ -673,90 +699,206 @@ class ModelSemanticColoring extends Disposable { }); const styling = this._semanticStyling.get(provider); - const request = Promise.resolve(provider.provideSemanticColoring(this._model, this._currentRequestCancellationTokenSource.token)); + + const lastResultId = this._currentResponse ? this._currentResponse.resultId || null : null; + const request = Promise.resolve(provider.provideSemanticTokens(this._model, lastResultId, null, this._currentRequestCancellationTokenSource.token)); request.then((res) => { this._currentRequestCancellationTokenSource = null; contentChangeListener.dispose(); - this._setSemanticTokens(res || null, styling, pendingChanges); + this._setSemanticTokens(provider, res || null, styling, pendingChanges); }, (err) => { errors.onUnexpectedError(err); this._currentRequestCancellationTokenSource = null; contentChangeListener.dispose(); - this._setSemanticTokens(null, styling, pendingChanges); + this._setSemanticTokens(provider, null, styling, pendingChanges); }); } - private _setSemanticTokens(tokens: SemanticColoring | null, styling: SemanticColoringProviderStyling | null, pendingChanges: IModelContentChangedEvent[]): void { + private static _isSemanticTokens(v: SemanticTokens | SemanticTokensEdits): v is SemanticTokens { + return v && !!((v).data); + } + + private static _isSemanticTokensEdits(v: SemanticTokens | SemanticTokensEdits): v is SemanticTokensEdits { + return v && Array.isArray((v).edits); + } + + private static _copy(src: Uint32Array, srcOffset: number, dest: Uint32Array, destOffset: number, length: number): void { + for (let i = 0; i < length; i++) { + dest[destOffset + i] = src[srcOffset + i]; + } + } + + private _setSemanticTokens(provider: SemanticTokensProvider | null, tokens: SemanticTokens | SemanticTokensEdits | null, styling: SemanticColoringProviderStyling | null, pendingChanges: IModelContentChangedEvent[]): void { + const currentResponse = this._currentResponse; if (this._currentResponse) { this._currentResponse.dispose(); this._currentResponse = null; } if (this._isDisposed) { // disposed! - if (tokens) { - tokens.dispose(); + if (provider && tokens) { + provider.releaseSemanticTokens(tokens.resultId); } return; } - this._currentResponse = tokens; - if (!this._currentResponse || !styling) { + if (!provider || !tokens || !styling) { this._model.setSemanticTokens(null); return; } - const result: MultilineTokens2[] = []; - for (const area of this._currentResponse.areas) { - const srcTokens = area.data; - const tokenCount = srcTokens.length / 5; - let destTokens = new Uint32Array(4 * tokenCount); - let destOffset = 0; - for (let i = 0; i < tokenCount; i++) { - const srcOffset = 5 * i; - const deltaLine = srcTokens[srcOffset]; - const startCharacter = srcTokens[srcOffset + 1]; - const endCharacter = srcTokens[srcOffset + 2]; - const tokenTypeIndex = srcTokens[srcOffset + 3]; - const tokenModifierSet = srcTokens[srcOffset + 4]; - const metadata = styling.getMetadata(tokenTypeIndex, tokenModifierSet); - if (metadata !== Constants.NO_STYLING) { - destTokens[destOffset] = deltaLine; - destTokens[destOffset + 1] = startCharacter; - destTokens[destOffset + 2] = endCharacter; - destTokens[destOffset + 3] = metadata; - destOffset += 4; + if (ModelSemanticColoring._isSemanticTokensEdits(tokens)) { + if (!currentResponse) { + // not possible! + this._model.setSemanticTokens(null); + return; + } + if (tokens.edits.length === 0) { + // nothing to do! + tokens = { + resultId: tokens.resultId, + data: currentResponse.data + }; + } else { + let deltaLength = 0; + for (const edit of tokens.edits) { + deltaLength += (edit.data ? edit.data.length : 0) - edit.deleteCount; } - } - if (destOffset !== destTokens.length) { - destTokens = destTokens.subarray(0, destOffset); + const srcData = currentResponse.data; + const destData = new Uint32Array(srcData.length + deltaLength); + + let srcLastStart = srcData.length; + let destLastStart = destData.length; + for (let i = tokens.edits.length - 1; i >= 0; i--) { + const edit = tokens.edits[i]; + + const copyCount = srcLastStart - (edit.start + edit.deleteCount); + if (copyCount > 0) { + ModelSemanticColoring._copy(srcData, srcLastStart - copyCount, destData, destLastStart - copyCount, copyCount); + destLastStart -= copyCount; + } + + if (edit.data) { + ModelSemanticColoring._copy(edit.data, 0, destData, destLastStart - edit.data.length, edit.data.length); + destLastStart -= edit.data.length; + } + + srcLastStart = edit.start; + } + + if (srcLastStart > 0) { + ModelSemanticColoring._copy(srcData, 0, destData, 0, srcLastStart); + } + + tokens = { + resultId: tokens.resultId, + data: destData + }; } - const tokens = new MultilineTokens2(area.line, new SparseEncodedTokens(destTokens)); - result.push(tokens); } - // Adjust incoming semantic tokens - if (pendingChanges.length > 0) { - // More changes occurred while the request was running - // We need to: - // 1. Adjust incoming semantic tokens - // 2. Request them again - for (const change of pendingChanges) { - for (const area of result) { - for (const singleChange of change.changes) { - area.applyEdit(singleChange.range, singleChange.text); + if (ModelSemanticColoring._isSemanticTokens(tokens)) { + + this._currentResponse = new SemanticTokensResponse(provider, tokens.resultId, tokens.data); + + const srcData = tokens.data; + const tokenCount = (tokens.data.length / 5) | 0; + const tokensPerArea = Math.max(Math.ceil(tokenCount / SemanticColoringConstants.DesiredMaxAreas), SemanticColoringConstants.DesiredTokensPerArea); + + const result: MultilineTokens2[] = []; + + let tokenIndex = 0; + let lastLineNumber = 1; + let lastStartCharacter = 0; + while (tokenIndex < tokenCount) { + const tokenStartIndex = tokenIndex; + let tokenEndIndex = Math.min(tokenStartIndex + tokensPerArea, tokenCount); + + // Keep tokens on the same line in the same area... + if (tokenEndIndex < tokenCount) { + + let smallTokenEndIndex = tokenEndIndex; + while (smallTokenEndIndex - 1 > tokenStartIndex && srcData[5 * smallTokenEndIndex] === 0) { + smallTokenEndIndex--; + } + + if (smallTokenEndIndex - 1 === tokenStartIndex) { + // there are so many tokens on this line that our area would be empty, we must now go right + let bigTokenEndIndex = tokenEndIndex; + while (bigTokenEndIndex + 1 < tokenCount && srcData[5 * bigTokenEndIndex] === 0) { + bigTokenEndIndex++; + } + tokenEndIndex = bigTokenEndIndex; + } else { + tokenEndIndex = smallTokenEndIndex; } } + + let destData = new Uint32Array((tokenEndIndex - tokenStartIndex) * 4); + let destOffset = 0; + let areaLine = 0; + while (tokenIndex < tokenEndIndex) { + const srcOffset = 5 * tokenIndex; + const deltaLine = srcData[srcOffset]; + const deltaCharacter = srcData[srcOffset + 1]; + const lineNumber = lastLineNumber + deltaLine; + const startCharacter = (deltaLine === 0 ? lastStartCharacter + deltaCharacter : deltaCharacter); + const length = srcData[srcOffset + 2]; + const tokenTypeIndex = srcData[srcOffset + 3]; + const tokenModifierSet = srcData[srcOffset + 4]; + const metadata = styling.getMetadata(tokenTypeIndex, tokenModifierSet); + + if (metadata !== Constants.NO_STYLING) { + if (areaLine === 0) { + areaLine = lineNumber; + } + destData[destOffset] = lineNumber - areaLine; + destData[destOffset + 1] = startCharacter; + destData[destOffset + 2] = startCharacter + length; + destData[destOffset + 3] = metadata; + destOffset += 4; + } + + lastLineNumber = lineNumber; + lastStartCharacter = startCharacter; + tokenIndex++; + } + + if (destOffset !== destData.length) { + destData = destData.subarray(0, destOffset); + } + + const tokens = new MultilineTokens2(areaLine, new SparseEncodedTokens(destData)); + result.push(tokens); } - this._fetchSemanticTokens.schedule(); + // Adjust incoming semantic tokens + if (pendingChanges.length > 0) { + // More changes occurred while the request was running + // We need to: + // 1. Adjust incoming semantic tokens + // 2. Request them again + for (const change of pendingChanges) { + for (const area of result) { + for (const singleChange of change.changes) { + area.applyEdit(singleChange.range, singleChange.text); + } + } + } + + this._fetchSemanticTokens.schedule(); + } + + this._model.setSemanticTokens(result); + return; } - this._model.setSemanticTokens(result); + this._model.setSemanticTokens(null); } - private _getSemanticColoringProvider(): SemanticColoringProvider | null { - const result = SemanticColoringProviderRegistry.ordered(this._model); + private _getSemanticColoringProvider(): SemanticTokensProvider | null { + const result = SemanticTokensProviderRegistry.ordered(this._model); return (result.length > 0 ? result[0] : null); } } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index b5e04b5c7ec..6f20419c1f3 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -5575,30 +5575,31 @@ declare namespace monaco.languages { resolveCodeLens?(model: editor.ITextModel, codeLens: CodeLens, token: CancellationToken): ProviderResult; } - export interface SemanticColoringLegend { + export interface SemanticTokensLegend { readonly tokenTypes: string[]; readonly tokenModifiers: string[]; } - export interface SemanticColoringArea { - /** - * The zero-based line value where this token block begins. - */ - readonly line: number; - /** - * The actual token block encoded data. - */ + export interface SemanticTokens { + readonly resultId?: string; readonly data: Uint32Array; } - export interface SemanticColoring { - readonly areas: SemanticColoringArea[]; - dispose(): void; + export interface SemanticTokensEdit { + readonly start: number; + readonly deleteCount: number; + readonly data?: Uint32Array; } - export interface SemanticColoringProvider { - getLegend(): SemanticColoringLegend; - provideSemanticColoring(model: editor.ITextModel, token: CancellationToken): ProviderResult; + export interface SemanticTokensEdits { + readonly resultId?: string; + readonly edits: SemanticTokensEdit[]; + } + + export interface SemanticTokensProvider { + getLegend(): SemanticTokensLegend; + provideSemanticTokens(model: editor.ITextModel, lastResultId: string | null, ranges: Range[] | null, token: CancellationToken): ProviderResult; + releaseSemanticTokens(resultId: string | undefined): void; } export interface ILanguageExtensionPoint { diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 7e1407dc6cc..5d2f4c6e87c 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -68,64 +68,77 @@ declare module 'vscode' { //#endregion - //#region Alex - semantic coloring + //#region Alex - semantic tokens - export class SemanticColoringLegend { + export class SemanticTokensLegend { public readonly tokenTypes: string[]; public readonly tokenModifiers: string[]; constructor(tokenTypes: string[], tokenModifiers: string[]); } - export class SemanticColoringArea { - /** - * The zero-based line value where this token block begins. - */ - public readonly line: number; - /** - * The actual token block encoded data. - * A certain token (at index `i` is encoded using 5 uint32 integers): - * - at index `5*i` - `deltaLine`: token line number, relative to `SemanticColoringArea.line` - * - at index `5*i+1` - `startCharacter`: token start character offset inside the line (inclusive) - * - at index `5*i+2` - `endCharacter`: token end character offset inside the line (exclusive) - * - at index `5*i+3` - `tokenType`: will be looked up in `SemanticColoringLegend.tokenTypes` - * - at index `5*i+4` - `tokenModifiers`: each set bit will be looked up in `SemanticColoringLegend.tokenModifiers` - */ - public readonly data: Uint32Array; - - constructor(line: number, data: Uint32Array); - } - - export class SemanticColoring { - public readonly areas: SemanticColoringArea[]; - - constructor(areas: SemanticColoringArea[]); + export class SemanticTokensBuilder { + constructor(); + push(line: number, char: number, length: number, tokenType: number, tokenModifiers: number): void; + build(): Uint32Array; } /** - * The semantic coloring provider interface defines the contract between extensions and - * semantic coloring. - * - * - */ - export interface SemanticColoringProvider { + * A certain token (at index `i` is encoded using 5 uint32 integers): + * - at index `5*i` - `deltaLine`: token line number, relative to `SemanticColoringArea.line` + * - at index `5*i+1` - `deltaStart`: token start character offset inside the line (relative to 0 or the previous token if they are on the same line) + * - at index `5*i+2` - `length`: the length of the token + * - at index `5*i+3` - `tokenType`: will be looked up in `SemanticColoringLegend.tokenTypes` + * - at index `5*i+4` - `tokenModifiers`: each set bit will be looked up in `SemanticColoringLegend.tokenModifiers` + */ + export class SemanticTokens { + readonly resultId?: string; + readonly data: Uint32Array; - provideSemanticColoring(document: TextDocument, token: CancellationToken): ProviderResult; + constructor(data: Uint32Array, resultId?: string); + } + + export class SemanticTokensEdits { + readonly resultId?: string; + readonly edits: SemanticTokensEdit[]; + + constructor(edits: SemanticTokensEdit[], resultId?: string); + } + + export class SemanticTokensEdit { + readonly start: number; + readonly deleteCount: number; + readonly data?: Uint32Array; + + constructor(start: number, deleteCount: number, data?: Uint32Array); + } + + export interface SemanticTokensRequestOptions { + readonly ranges?: readonly Range[]; + readonly previousResultId?: string; + } + + /** + * The semantic tokens provider interface defines the contract between extensions and + * semantic tokens. + */ + export interface SemanticTokensProvider { + provideSemanticTokens(document: TextDocument, options: SemanticTokensRequestOptions, token: CancellationToken): ProviderResult; } export namespace languages { /** - * Register a semantic coloring provider. + * Register a semantic tokens provider. * * Multiple providers can be registered for a language. In that case providers are sorted * by their [score](#languages.match) and the best-matching provider is used. Failure * of the selected provider will cause a failure of the whole operation. * * @param selector A selector that defines the documents this provider is applicable to. - * @param provider A semantic coloring provider. + * @param provider A semantic tokens provider. * @return A [disposable](#Disposable) that unregisters this provider when being disposed. */ - export function registerSemanticColoringProvider(selector: DocumentSelector, provider: SemanticColoringProvider, legend: SemanticColoringLegend): Disposable; + export function registerSemanticTokensProvider(selector: DocumentSelector, provider: SemanticTokensProvider, legend: SemanticTokensLegend): Disposable; } //#endregion diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 8b3849988cc..05447ed8a4b 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -21,7 +21,7 @@ import { Selection } from 'vs/editor/common/core/selection'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import * as callh from 'vs/workbench/contrib/callHierarchy/browser/callHierarchy'; import { mixin } from 'vs/base/common/objects'; -import { decodeSemanticTokensDto, ISemanticTokensDto } from 'vs/workbench/api/common/shared/semanticTokens'; +import { decodeSemanticTokensDto } from 'vs/workbench/api/common/shared/semanticTokens'; @extHostNamedCustomer(MainContext.MainThreadLanguageFeatures) export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesShape { @@ -325,10 +325,10 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha })); } - // --- semantic coloring + // --- semantic tokens - $registerSemanticColoringProvider(handle: number, selector: IDocumentFilterDto[], legend: modes.SemanticColoringLegend): void { - this._registrations.set(handle, modes.SemanticColoringProviderRegistry.register(selector, new MainThreadSemanticColoringProvider(this._proxy, handle, legend))); + $registerSemanticTokensProvider(handle: number, selector: IDocumentFilterDto[], legend: modes.SemanticTokensLegend): void { + this._registrations.set(handle, modes.SemanticTokensProviderRegistry.register(selector, new MainThreadSemanticTokensProvider(this._proxy, handle, legend))); } // --- suggest @@ -602,47 +602,28 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha } -class MainThreadSemanticColoringCacheEntry implements modes.SemanticColoring { - - constructor( - private readonly _parent: MainThreadSemanticColoringProvider, - public readonly uri: URI, - public readonly id: number, - public readonly areas: modes.SemanticColoringArea[], - ) { - } - - dispose(): void { - this._parent.release(this); - } -} - -export class MainThreadSemanticColoringProvider implements modes.SemanticColoringProvider { - - private readonly _cache = new Map(); +export class MainThreadSemanticTokensProvider implements modes.SemanticTokensProvider { constructor( private readonly _proxy: ExtHostLanguageFeaturesShape, private readonly _handle: number, - private readonly _legend: modes.SemanticColoringLegend, + private readonly _legend: modes.SemanticTokensLegend, ) { } - release(entry: MainThreadSemanticColoringCacheEntry): void { - const currentCacheEntry = this._cache.get(entry.uri.toString()) || null; - if (currentCacheEntry && currentCacheEntry.id === entry.id) { - this._cache.delete(entry.uri.toString()); + public releaseSemanticTokens(resultId: string | undefined): void { + if (resultId) { + this._proxy.$releaseSemanticTokens(this._handle, parseInt(resultId, 10)); } - this._proxy.$releaseSemanticColoring(this._handle, entry.id); } - getLegend(): modes.SemanticColoringLegend { + public getLegend(): modes.SemanticTokensLegend { return this._legend; } - async provideSemanticColoring(model: ITextModel, token: CancellationToken): Promise { - const lastResult = this._cache.get(model.uri.toString()) || null; - const encodedDto = await this._proxy.$provideSemanticColoring(this._handle, model.uri, lastResult ? lastResult.id : 0, token); + async provideSemanticTokens(model: ITextModel, lastResultId: string | null, ranges: EditorRange[] | null, token: CancellationToken): Promise { + const nLastResultId = lastResultId ? parseInt(lastResultId, 10) : 0; + const encodedDto = await this._proxy.$provideSemanticTokens(this._handle, model.uri, ranges, nLastResultId, token); if (!encodedDto) { return null; } @@ -650,27 +631,15 @@ export class MainThreadSemanticColoringProvider implements modes.SemanticColorin return null; } const dto = decodeSemanticTokensDto(encodedDto); - const res = this._resolveDeltas(model, lastResult, dto); - this._cache.set(model.uri.toString(), res); - return res; - } - - private _resolveDeltas(model: ITextModel, lastResult: MainThreadSemanticColoringCacheEntry | null, dto: ISemanticTokensDto): MainThreadSemanticColoringCacheEntry { - let areas: modes.SemanticColoringArea[] = []; - for (let i = 0, len = dto.areas.length; i < len; i++) { - const areaDto = dto.areas[i]; - if (areaDto.type === 'full') { - areas[i] = { - line: areaDto.line, - data: areaDto.data - }; - } else { - areas[i] = { - line: areaDto.line, - data: lastResult!.areas[areaDto.oldIndex].data - }; - } + if (dto.type === 'full') { + return { + resultId: String(dto.id), + data: dto.data + }; } - return new MainThreadSemanticColoringCacheEntry(this, model.uri, dto.id, areas); + return { + resultId: String(dto.id), + edits: dto.deltas + }; } } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 84a355928e9..9e32aedf532 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -351,9 +351,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I registerOnTypeFormattingEditProvider(selector: vscode.DocumentSelector, provider: vscode.OnTypeFormattingEditProvider, firstTriggerCharacter: string, ...moreTriggerCharacters: string[]): vscode.Disposable { return extHostLanguageFeatures.registerOnTypeFormattingEditProvider(extension, checkSelector(selector), provider, [firstTriggerCharacter].concat(moreTriggerCharacters)); }, - registerSemanticColoringProvider(selector: vscode.DocumentSelector, provider: vscode.SemanticColoringProvider, legend: vscode.SemanticColoringLegend): vscode.Disposable { + registerSemanticTokensProvider(selector: vscode.DocumentSelector, provider: vscode.SemanticTokensProvider, legend: vscode.SemanticTokensLegend): vscode.Disposable { checkProposedApiEnabled(extension); - return extHostLanguageFeatures.registerSemanticColoringProvider(extension, checkSelector(selector), provider, legend); + return extHostLanguageFeatures.registerSemanticTokensProvider(extension, checkSelector(selector), provider, legend); }, registerSignatureHelpProvider(selector: vscode.DocumentSelector, provider: vscode.SignatureHelpProvider, firstItem?: string | vscode.SignatureHelpProviderMetadata, ...remaining: string[]): vscode.Disposable { if (typeof firstItem === 'object') { @@ -893,9 +893,11 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I RelativePattern: extHostTypes.RelativePattern, ResolvedAuthority: extHostTypes.ResolvedAuthority, RemoteAuthorityResolverError: extHostTypes.RemoteAuthorityResolverError, - SemanticColoring: extHostTypes.SemanticColoring, - SemanticColoringArea: extHostTypes.SemanticColoringArea, - SemanticColoringLegend: extHostTypes.SemanticColoringLegend, + SemanticTokensLegend: extHostTypes.SemanticTokensLegend, + SemanticTokensBuilder: extHostTypes.SemanticTokensBuilder, + SemanticTokens: extHostTypes.SemanticTokens, + SemanticTokensEdits: extHostTypes.SemanticTokensEdits, + SemanticTokensEdit: extHostTypes.SemanticTokensEdit, Selection: extHostTypes.Selection, SelectionRange: extHostTypes.SelectionRange, ShellExecution: extHostTypes.ShellExecution, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 290c93a353f..4bf6846bd20 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -354,7 +354,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $registerOnTypeFormattingSupport(handle: number, selector: IDocumentFilterDto[], autoFormatTriggerCharacters: string[], extensionId: ExtensionIdentifier): void; $registerNavigateTypeSupport(handle: number): void; $registerRenameSupport(handle: number, selector: IDocumentFilterDto[], supportsResolveInitialValues: boolean): void; - $registerSemanticColoringProvider(handle: number, selector: IDocumentFilterDto[], legend: modes.SemanticColoringLegend): void; + $registerSemanticTokensProvider(handle: number, selector: IDocumentFilterDto[], legend: modes.SemanticTokensLegend): void; $registerSuggestSupport(handle: number, selector: IDocumentFilterDto[], triggerCharacters: string[], supportsResolveDetails: boolean, extensionId: ExtensionIdentifier): void; $registerSignatureHelpProvider(handle: number, selector: IDocumentFilterDto[], metadata: ISignatureHelpProviderMetadataDto): void; $registerDocumentLinkProvider(handle: number, selector: IDocumentFilterDto[], supportsResolve: boolean): void; @@ -1167,8 +1167,8 @@ export interface ExtHostLanguageFeaturesShape { $releaseWorkspaceSymbols(handle: number, id: number): void; $provideRenameEdits(handle: number, resource: UriComponents, position: IPosition, newName: string, token: CancellationToken): Promise; $resolveRenameLocation(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; - $provideSemanticColoring(handle: number, resource: UriComponents, previousSemanticColoringResultId: number, token: CancellationToken): Promise; - $releaseSemanticColoring(handle: number, semanticColoringResultId: number): void; + $provideSemanticTokens(handle: number, resource: UriComponents, ranges: IRange[] | null, previousResultId: number, token: CancellationToken): Promise; + $releaseSemanticTokens(handle: number, semanticColoringResultId: number): void; $provideCompletionItems(handle: number, resource: UriComponents, position: IPosition, context: modes.CompletionContext, token: CancellationToken): Promise; $resolveCompletionItem(handle: number, resource: UriComponents, position: IPosition, id: ChainedCacheId, token: CancellationToken): Promise; $releaseCompletionItems(handle: number, id: number): void; diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 88464ef0ead..3aa55dc26da 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -7,7 +7,7 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { mixin } from 'vs/base/common/objects'; import * as vscode from 'vscode'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; -import { Range, Disposable, CompletionList, SnippetString, CodeActionKind, SymbolInformation, DocumentSymbol, SemanticColoringArea } from 'vs/workbench/api/common/extHostTypes'; +import { Range, Disposable, CompletionList, SnippetString, CodeActionKind, SymbolInformation, DocumentSymbol, SemanticTokensEdits } from 'vs/workbench/api/common/extHostTypes'; import { ISingleEditOperation } from 'vs/editor/common/model'; import * as modes from 'vs/editor/common/modes'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; @@ -27,7 +27,7 @@ import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensio import { IURITransformer } from 'vs/base/common/uriIpc'; import { DisposableStore, dispose } from 'vs/base/common/lifecycle'; import { VSBuffer } from 'vs/base/common/buffer'; -import { encodeSemanticTokensDto, ISemanticTokensDto, ISemanticTokensAreaDto } from 'vs/workbench/api/common/shared/semanticTokens'; +import { encodeSemanticTokensDto } from 'vs/workbench/api/common/shared/semanticTokens'; import { IdGenerator } from 'vs/base/common/idGenerator'; // --- adapter @@ -616,62 +616,40 @@ class RenameAdapter { } } -export const enum SemanticColoringConstants { - /** - * Let's aim at having 8KB buffers if possible... - * So that would be 8192 / (5 * 4) = 409.6 tokens per area - */ - DesiredTokensPerArea = 400, - - /** - * Try to keep the total number of areas under 1024 if possible, - * simply compensate by having more tokens per area... - */ - DesiredMaxAreas = 1024, - - /** - * Threshold for merging multiple delta areas and sending a full area. - */ - MinTokensPerArea = 50 +class SemanticTokensPreviousResult { + constructor( + public readonly resultId: string | undefined, + public readonly tokens?: Uint32Array, + ) { } } -interface ISemanticColoringAreaPair { - data: Uint32Array; - dto: ISemanticTokensAreaDto; -} +export class SemanticTokensAdapter { -export class SemanticColoringAdapter { - - private readonly _previousResults: Map; - private readonly _splitSingleAreaTokenCountThreshold: number; + private readonly _previousResults: Map; private _nextResultId = 1; constructor( private readonly _documents: ExtHostDocuments, - private readonly _provider: vscode.SemanticColoringProvider, - private readonly _desiredTokensPerArea = SemanticColoringConstants.DesiredTokensPerArea, - private readonly _desiredMaxAreas = SemanticColoringConstants.DesiredMaxAreas, - private readonly _minTokensPerArea = SemanticColoringConstants.MinTokensPerArea + private readonly _provider: vscode.SemanticTokensProvider, ) { - this._previousResults = new Map(); - this._splitSingleAreaTokenCountThreshold = Math.round(1.5 * this._desiredTokensPerArea); + this._previousResults = new Map(); } - provideSemanticColoring(resource: URI, previousSemanticColoringResultId: number, token: CancellationToken): Promise { + provideSemanticTokens(resource: URI, ranges: IRange[] | null, previousResultId: number, token: CancellationToken): Promise { const doc = this._documents.getDocument(resource); - - return asPromise(() => this._provider.provideSemanticColoring(doc, token)).then(value => { + const previousResult = (previousResultId !== 0 ? this._previousResults.get(previousResultId) : null); + const opts: vscode.SemanticTokensRequestOptions = { + ranges: (Array.isArray(ranges) && ranges.length > 0 ? ranges.map(typeConvert.Range.to) : undefined), + previousResultId: (previousResult ? previousResult.resultId : undefined) + }; + return asPromise(() => this._provider.provideSemanticTokens(doc, opts, token)).then(value => { if (!value) { return null; } - - const oldAreas = (previousSemanticColoringResultId !== 0 ? this._previousResults.get(previousSemanticColoringResultId) : null); - if (oldAreas) { - this._previousResults.delete(previousSemanticColoringResultId); - return this._deltaEncodeAreas(oldAreas, value.areas); + if (previousResult) { + this._previousResults.delete(previousResultId); } - - return this._fullEncodeAreas(value.areas); + return this._send(SemanticTokensAdapter._convertToEdits(previousResult, value), value); }); } @@ -679,298 +657,77 @@ export class SemanticColoringAdapter { this._previousResults.delete(semanticColoringResultId); } - private _deltaEncodeAreas(oldAreas: Uint32Array[], newAreas: SemanticColoringArea[]): VSBuffer { - if (newAreas.length > 1) { - // this is a fancy provider which is smart enough to break things into good areas - // we therefore try to match old areas only by object identity - const oldAreasIndexMap = new Map(); - for (let i = 0, len = oldAreas.length; i < len; i++) { - oldAreasIndexMap.set(oldAreas[i], i); - } - - let result: ISemanticColoringAreaPair[] = []; - for (let i = 0, len = newAreas.length; i < len; i++) { - const newArea = newAreas[i]; - if (oldAreasIndexMap.has(newArea.data)) { - // great! we can reuse this area - const oldIndex = oldAreasIndexMap.get(newArea.data)!; - result.push({ - data: newArea.data, - dto: { - type: 'delta', - line: newArea.line, - oldIndex: oldIndex - } - }); - } else { - result.push({ - data: newArea.data, - dto: { - type: 'full', - line: newArea.line, - data: newArea.data - } - }); - } - } - - return this._saveResultAndEncode(result); - } - - return this._deltaEncodeArea(oldAreas, newAreas[0]); + private static _isSemanticTokens(v: vscode.SemanticTokens | vscode.SemanticTokensEdits): v is vscode.SemanticTokens { + return v && !!((v as vscode.SemanticTokens).data); } - private static _oldAreaAppearsInNewArea(oldAreaData: Uint32Array, oldAreaTokenCount: number, newAreaData: Uint32Array, newAreaOffset: number): boolean { - const newTokenStartDeltaLine = newAreaData[5 * newAreaOffset]; - - // check that each and every value from `oldArea` is equal to `area` - for (let j = 0; j < oldAreaTokenCount; j++) { - const oldOffset = 5 * j; - const newOffset = 5 * (j + newAreaOffset); - - if ( - (oldAreaData[oldOffset] !== newAreaData[newOffset] - newTokenStartDeltaLine) - || (oldAreaData[oldOffset + 1] !== newAreaData[newOffset + 1]) - || (oldAreaData[oldOffset + 2] !== newAreaData[newOffset + 2]) - || (oldAreaData[oldOffset + 3] !== newAreaData[newOffset + 3]) - || (oldAreaData[oldOffset + 4] !== newAreaData[newOffset + 4]) - ) { - return false; - } - } - - return true; + private static _isSemanticTokensEdits(v: vscode.SemanticTokens | vscode.SemanticTokensEdits): v is vscode.SemanticTokensEdits { + return v && Array.isArray((v as vscode.SemanticTokensEdits).edits); } - private _deltaEncodeArea(oldAreas: Uint32Array[], newArea: SemanticColoringArea): VSBuffer { - const newAreaData = newArea.data; - const prependAreas: ISemanticColoringAreaPair[] = []; - const appendAreas: ISemanticColoringAreaPair[] = []; + private static _convertToEdits(previousResult: SemanticTokensPreviousResult | null | undefined, newResult: vscode.SemanticTokens | vscode.SemanticTokensEdits): vscode.SemanticTokens | vscode.SemanticTokensEdits { + if (!SemanticTokensAdapter._isSemanticTokens(newResult)) { + return newResult; + } + if (!previousResult || !previousResult.tokens) { + return newResult; + } + const oldData = previousResult.tokens; + const oldLength = oldData.length; + const newData = newResult.data; + const newLength = newData.length; - // Try to find appearences of `oldAreas` inside `area`. - let newTokenStartIndex = 0; - let newTokenEndIndex = (newAreaData.length / 5) | 0; - let oldAreaUsedIndex = -1; - for (let i = 0, len = oldAreas.length; i < len; i++) { - const oldAreaData = oldAreas[i]; - const oldAreaTokenCount = (oldAreaData.length / 5) | 0; - if (oldAreaTokenCount === 0) { - // skip old empty areas - continue; - } - if (newTokenEndIndex - newTokenStartIndex < oldAreaTokenCount) { - // there are too many old tokens, this cannot work - break; - } + let commonPrefixLength = 0; + const maxCommonPrefixLength = Math.min(oldLength, newLength); + while (commonPrefixLength < maxCommonPrefixLength && oldData[commonPrefixLength] === newData[commonPrefixLength]) { + commonPrefixLength++; + } - const newAreaOffset = newTokenStartIndex; - const newTokenStartDeltaLine = newAreaData[5 * newAreaOffset]; - const isEqual = SemanticColoringAdapter._oldAreaAppearsInNewArea(oldAreaData, oldAreaTokenCount, newAreaData, newAreaOffset); - if (!isEqual) { - break; - } - newTokenStartIndex += oldAreaTokenCount; + if (commonPrefixLength === oldLength && commonPrefixLength === newLength) { + // complete overlap! + return new SemanticTokensEdits([], newResult.resultId); + } - oldAreaUsedIndex = i; - prependAreas.push({ - data: oldAreaData, - dto: { - type: 'delta', - line: newArea.line + newTokenStartDeltaLine, - oldIndex: i - } + let commonSuffixLength = 0; + const maxCommonSuffixLength = maxCommonPrefixLength - commonPrefixLength; + while (commonSuffixLength < maxCommonSuffixLength && oldData[oldLength - commonSuffixLength - 1] === newData[newLength - commonSuffixLength - 1]) { + commonSuffixLength++; + } + + return new SemanticTokensEdits([{ + start: commonPrefixLength, + deleteCount: (oldLength - commonPrefixLength - commonSuffixLength), + data: newData.subarray(commonPrefixLength, newLength - commonSuffixLength) + }], newResult.resultId); + } + + private _send(value: vscode.SemanticTokens | vscode.SemanticTokensEdits, original: vscode.SemanticTokens | vscode.SemanticTokensEdits): VSBuffer | null { + if (SemanticTokensAdapter._isSemanticTokens(value)) { + const myId = this._nextResultId++; + this._previousResults.set(myId, new SemanticTokensPreviousResult(value.resultId, value.data)); + return encodeSemanticTokensDto({ + id: myId, + type: 'full', + data: value.data }); } - for (let i = oldAreas.length - 1; i > oldAreaUsedIndex; i--) { - const oldAreaData = oldAreas[i]; - const oldAreaTokenCount = (oldAreaData.length / 5) | 0; - if (oldAreaTokenCount === 0) { - // skip old empty areas - continue; + if (SemanticTokensAdapter._isSemanticTokensEdits(value)) { + const myId = this._nextResultId++; + if (SemanticTokensAdapter._isSemanticTokens(original)) { + // store the original + this._previousResults.set(myId, new SemanticTokensPreviousResult(original.resultId, original.data)); + } else { + this._previousResults.set(myId, new SemanticTokensPreviousResult(value.resultId)); } - if (newTokenEndIndex - newTokenStartIndex < oldAreaTokenCount) { - // there are too many old tokens, this cannot work - break; - } - - const newAreaOffset = (newTokenEndIndex - oldAreaTokenCount); - const newTokenStartDeltaLine = newAreaData[5 * newAreaOffset]; - const isEqual = SemanticColoringAdapter._oldAreaAppearsInNewArea(oldAreaData, oldAreaTokenCount, newAreaData, newAreaOffset); - if (!isEqual) { - break; - } - newTokenEndIndex -= oldAreaTokenCount; - - appendAreas.unshift({ - data: oldAreaData, - dto: { - type: 'delta', - line: newArea.line + newTokenStartDeltaLine, - oldIndex: i - } + return encodeSemanticTokensDto({ + id: myId, + type: 'delta', + deltas: (value.edits || []).map(edit => ({ start: edit.start, deleteCount: edit.deleteCount, data: edit.data })) }); } - if (prependAreas.length === 0 && appendAreas.length === 0) { - // There is no reuse possibility! - return this._fullEncodeAreas([newArea]); - } - - if (newTokenStartIndex === newTokenEndIndex) { - // 100% reuse! - return this._saveResultAndEncode(prependAreas.concat(appendAreas)); - } - - // It is clear at this point that there will be at least one full area. - // Expand the mid area if the areas next to it are too small - while (prependAreas.length > 0) { - const tokenCount = (prependAreas[prependAreas.length - 1].data.length / 5); - if (tokenCount < this._minTokensPerArea) { - newTokenStartIndex -= tokenCount; - prependAreas.pop(); - } else { - break; - } - } - while (appendAreas.length > 0) { - const tokenCount = (appendAreas[0].data.length / 5); - if (tokenCount < this._minTokensPerArea) { - newTokenEndIndex += tokenCount; - appendAreas.shift(); - } else { - break; - } - } - - // Extract the mid area - const newTokenStartDeltaLine = newAreaData[5 * newTokenStartIndex]; - const newMidAreaData = new Uint32Array(5 * (newTokenEndIndex - newTokenStartIndex)); - for (let tokenIndex = newTokenStartIndex; tokenIndex < newTokenEndIndex; tokenIndex++) { - const srcOffset = 5 * tokenIndex; - const deltaLine = newAreaData[srcOffset]; - const startCharacter = newAreaData[srcOffset + 1]; - const endCharacter = newAreaData[srcOffset + 2]; - const tokenType = newAreaData[srcOffset + 3]; - const tokenModifiers = newAreaData[srcOffset + 4]; - - const destOffset = 5 * (tokenIndex - newTokenStartIndex); - newMidAreaData[destOffset] = deltaLine - newTokenStartDeltaLine; - newMidAreaData[destOffset + 1] = startCharacter; - newMidAreaData[destOffset + 2] = endCharacter; - newMidAreaData[destOffset + 3] = tokenType; - newMidAreaData[destOffset + 4] = tokenModifiers; - } - - const newMidArea = new SemanticColoringArea(newArea.line + newTokenStartDeltaLine, newMidAreaData); - const newMidAreas = this._splitAreaIntoMultipleAreasIfNecessary(newMidArea); - const newMidAreasPairs: ISemanticColoringAreaPair[] = newMidAreas.map(a => { - return { - data: a.data, - dto: { - type: 'full', - line: a.line, - data: a.data, - } - }; - }); - - return this._saveResultAndEncode(prependAreas.concat(newMidAreasPairs).concat(appendAreas)); - } - - private _fullEncodeAreas(areas: SemanticColoringArea[]): VSBuffer { - if (areas.length === 1) { - areas = this._splitAreaIntoMultipleAreasIfNecessary(areas[0]); - } - - return this._saveResultAndEncode(areas.map(a => { - return { - data: a.data, - dto: { - type: 'full', - line: a.line, - data: a.data - } - }; - })); - } - - private _saveResultAndEncode(areas: ISemanticColoringAreaPair[]): VSBuffer { - const myId = this._nextResultId++; - this._previousResults.set(myId, areas.map(a => a.data)); - console.log(`_saveResultAndEncode: ${myId} --> ${areas.map(a => `${a.dto.line}-${a.dto.type}(${a.data.length / 5})`).join(', ')}`); - const dto: ISemanticTokensDto = { - id: myId, - areas: areas.map(a => a.dto) - }; - return encodeSemanticTokensDto(dto); - } - - private _splitAreaIntoMultipleAreasIfNecessary(area: vscode.SemanticColoringArea): SemanticColoringArea[] { - const srcAreaLine = area.line; - const srcAreaData = area.data; - const tokenCount = (srcAreaData.length / 5) | 0; - if (tokenCount <= this._splitSingleAreaTokenCountThreshold) { - return [area]; - } - - const tokensPerArea = Math.max(Math.ceil(tokenCount / this._desiredMaxAreas), this._desiredTokensPerArea); - - let result: SemanticColoringArea[] = []; - let tokenIndex = 0; - while (tokenIndex < tokenCount) { - const tokenStartIndex = tokenIndex; - let tokenEndIndex = Math.min(tokenStartIndex + tokensPerArea, tokenCount); - - // Keep tokens on the same line in the same area... - if (tokenEndIndex < tokenCount) { - let smallAvoidDeltaLine = srcAreaData[5 * tokenEndIndex]; - let smallTokenEndIndex = tokenEndIndex; - while (smallTokenEndIndex - 1 > tokenStartIndex && srcAreaData[5 * (smallTokenEndIndex - 1)] === smallAvoidDeltaLine) { - smallTokenEndIndex--; - } - - if (smallTokenEndIndex - 1 === tokenStartIndex) { - // there are so many tokens on this line that our area would be empty, we must now go right - let bigAvoidDeltaLine = srcAreaData[5 * (tokenEndIndex - 1)]; - let bigTokenEndIndex = tokenEndIndex; - while (bigTokenEndIndex + 1 < tokenCount && srcAreaData[5 * (bigTokenEndIndex + 1)] === bigAvoidDeltaLine) { - bigTokenEndIndex++; - } - tokenEndIndex = bigTokenEndIndex; - } else { - tokenEndIndex = smallTokenEndIndex; - } - } - - let destAreaLine = 0; - const destAreaData = new Uint32Array((tokenEndIndex - tokenStartIndex) * 5); - while (tokenIndex < tokenEndIndex) { - const srcOffset = 5 * tokenIndex; - const line = srcAreaLine + srcAreaData[srcOffset]; - const startCharacter = srcAreaData[srcOffset + 1]; - const endCharacter = srcAreaData[srcOffset + 2]; - const tokenType = srcAreaData[srcOffset + 3]; - const tokenModifiers = srcAreaData[srcOffset + 4]; - - if (tokenIndex === tokenStartIndex) { - destAreaLine = line; - } - - const destOffset = 5 * (tokenIndex - tokenStartIndex); - destAreaData[destOffset] = line - destAreaLine; - destAreaData[destOffset + 1] = startCharacter; - destAreaData[destOffset + 2] = endCharacter; - destAreaData[destOffset + 3] = tokenType; - destAreaData[destOffset + 4] = tokenModifiers; - - tokenIndex++; - } - - result.push(new SemanticColoringArea(destAreaLine, destAreaData)); - } - - return result; + return null; } } @@ -1481,7 +1238,7 @@ class CallHierarchyAdapter { type Adapter = DocumentSymbolAdapter | CodeLensAdapter | DefinitionAdapter | HoverAdapter | DocumentHighlightAdapter | ReferenceAdapter | CodeActionAdapter | DocumentFormattingAdapter | RangeFormattingAdapter | OnTypeFormattingAdapter | NavigateTypeAdapter | RenameAdapter - | SemanticColoringAdapter | SuggestAdapter | SignatureHelpAdapter | LinkProviderAdapter + | SemanticTokensAdapter | SuggestAdapter | SignatureHelpAdapter | LinkProviderAdapter | ImplementationAdapter | TypeDefinitionAdapter | ColorProviderAdapter | FoldingProviderAdapter | DeclarationAdapter | SelectionRangeAdapter | CallHierarchyAdapter; @@ -1809,18 +1566,18 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF //#region semantic coloring - registerSemanticColoringProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.SemanticColoringProvider, legend: vscode.SemanticColoringLegend): vscode.Disposable { - const handle = this._addNewAdapter(new SemanticColoringAdapter(this._documents, provider), extension); - this._proxy.$registerSemanticColoringProvider(handle, this._transformDocumentSelector(selector), legend); + registerSemanticTokensProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.SemanticTokensProvider, legend: vscode.SemanticTokensLegend): vscode.Disposable { + const handle = this._addNewAdapter(new SemanticTokensAdapter(this._documents, provider), extension); + this._proxy.$registerSemanticTokensProvider(handle, this._transformDocumentSelector(selector), legend); return this._createDisposable(handle); } - $provideSemanticColoring(handle: number, resource: UriComponents, previousSemanticColoringResultId: number, token: CancellationToken): Promise { - return this._withAdapter(handle, SemanticColoringAdapter, adapter => adapter.provideSemanticColoring(URI.revive(resource), previousSemanticColoringResultId, token), null); + $provideSemanticTokens(handle: number, resource: UriComponents, ranges: IRange[] | null, previousResultId: number, token: CancellationToken): Promise { + return this._withAdapter(handle, SemanticTokensAdapter, adapter => adapter.provideSemanticTokens(URI.revive(resource), ranges, previousResultId, token), null); } - $releaseSemanticColoring(handle: number, semanticColoringResultId: number): void { - this._withAdapter(handle, SemanticColoringAdapter, adapter => adapter.releaseSemanticColoring(semanticColoringResultId), undefined); + $releaseSemanticTokens(handle: number, semanticColoringResultId: number): void { + this._withAdapter(handle, SemanticTokensAdapter, adapter => adapter.releaseSemanticColoring(semanticColoringResultId), undefined); } //#endregion diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 4573846bd4d..8f89d246b5a 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -2368,7 +2368,7 @@ export enum CommentMode { //#region Semantic Coloring -export class SemanticColoringLegend { +export class SemanticTokensLegend { public readonly tokenTypes: string[]; public readonly tokenModifiers: string[]; @@ -2378,21 +2378,74 @@ export class SemanticColoringLegend { } } -export class SemanticColoringArea { - public readonly line: number; - public readonly data: Uint32Array; +export class SemanticTokensBuilder { - constructor(line: number, data: Uint32Array) { - this.line = line; + private _prevLine: number; + private _prevChar: number; + private _data: number[]; + private _dataLen: number; + + constructor() { + this._prevLine = 0; + this._prevChar = 0; + this._data = []; + this._dataLen = 0; + } + + public push(line: number, char: number, length: number, tokenType: number, tokenModifiers: number): void { + let pushLine = line; + let pushChar = char; + if (this._dataLen > 0) { + pushLine -= this._prevLine; + if (pushLine === 0) { + pushChar -= this._prevChar; + } + } + + this._data[this._dataLen++] = pushLine; + this._data[this._dataLen++] = pushChar; + this._data[this._dataLen++] = length; + this._data[this._dataLen++] = tokenType; + this._data[this._dataLen++] = tokenModifiers; + + this._prevLine = line; + this._prevChar = char; + } + + public build(): Uint32Array { + return new Uint32Array(this._data); + } +} + +export class SemanticTokens { + readonly resultId?: string; + readonly data: Uint32Array; + + constructor(data: Uint32Array, resultId?: string) { + this.resultId = resultId; this.data = data; } } -export class SemanticColoring { - public readonly areas: SemanticColoringArea[]; +export class SemanticTokensEdit { + readonly start: number; + readonly deleteCount: number; + readonly data?: Uint32Array; - constructor(areas: SemanticColoringArea[]) { - this.areas = areas; + constructor(start: number, deleteCount: number, data?: Uint32Array) { + this.start = start; + this.deleteCount = deleteCount; + this.data = data; + } +} + +export class SemanticTokensEdits { + readonly resultId?: string; + readonly edits: SemanticTokensEdit[]; + + constructor(edits: SemanticTokensEdit[], resultId?: string) { + this.resultId = resultId; + this.edits = edits; } } diff --git a/src/vs/workbench/api/common/shared/semanticTokens.ts b/src/vs/workbench/api/common/shared/semanticTokens.ts index 0d7073ca65d..adce8e3bf58 100644 --- a/src/vs/workbench/api/common/shared/semanticTokens.ts +++ b/src/vs/workbench/api/common/shared/semanticTokens.ts @@ -5,47 +5,71 @@ import { VSBuffer } from 'vs/base/common/buffer'; -export interface ISemanticTokensFullAreaDto { +export interface IFullSemanticTokensDto { + id: number; type: 'full'; - line: number; data: Uint32Array; } -export interface ISemanticTokensDeltaAreaDto { - type: 'delta'; - line: number; - oldIndex: number; -} - -export type ISemanticTokensAreaDto = ISemanticTokensFullAreaDto | ISemanticTokensDeltaAreaDto; - -export interface ISemanticTokensDto { +export interface IDeltaSemanticTokensDto { id: number; - areas: ISemanticTokensAreaDto[]; + type: 'delta'; + deltas: { start: number; deleteCount: number; data?: Uint32Array; }[]; } -const enum EncodedSemanticTokensAreaType { +export type ISemanticTokensDto = IFullSemanticTokensDto | IDeltaSemanticTokensDto; + +const enum EncodedSemanticTokensType { Full = 1, Delta = 2 } export function encodeSemanticTokensDto(semanticTokens: ISemanticTokensDto): VSBuffer { - const buff = VSBuffer.alloc(encodedSize(semanticTokens)); + const buff = VSBuffer.alloc(encodedSize2(semanticTokens)); let offset = 0; buff.writeUInt32BE(semanticTokens.id, offset); offset += 4; - buff.writeUInt32BE(semanticTokens.areas.length, offset); offset += 4; - for (let i = 0; i < semanticTokens.areas.length; i++) { - offset = encodeArea(semanticTokens.areas[i], buff, offset); + if (semanticTokens.type === 'full') { + buff.writeUInt8(EncodedSemanticTokensType.Full, offset); offset += 1; + buff.writeUInt32BE(semanticTokens.data.length, offset); offset += 4; + for (const uint of semanticTokens.data) { + buff.writeUInt32BE(uint, offset); offset += 4; + } + } else { + buff.writeUInt8(EncodedSemanticTokensType.Delta, offset); offset += 1; + buff.writeUInt32BE(semanticTokens.deltas.length, offset); offset += 4; + for (const delta of semanticTokens.deltas) { + buff.writeUInt32BE(delta.start, offset); offset += 4; + buff.writeUInt32BE(delta.deleteCount, offset); offset += 4; + if (delta.data) { + buff.writeUInt32BE(delta.data.length, offset); offset += 4; + for (const uint of delta.data) { + buff.writeUInt32BE(uint, offset); offset += 4; + } + } else { + buff.writeUInt32BE(0, offset); offset += 4; + } + } } return buff; } -function encodedSize(semanticTokens: ISemanticTokensDto): number { +function encodedSize2(semanticTokens: ISemanticTokensDto): number { let result = 0; - result += 4; // etag - result += 4; // area count - for (let i = 0; i < semanticTokens.areas.length; i++) { - result += encodedAreaSize(semanticTokens.areas[i]); + result += 4; // id + result += 1; // type + if (semanticTokens.type === 'full') { + result += 4; // data length + result += semanticTokens.data.byteLength; + } else { + result += 4; // delta count + for (const delta of semanticTokens.deltas) { + result += 4; // start + result += 4; // deleteCount + result += 4; // data length + if (delta.data) { + result += delta.data.byteLength; + } + } } return result; } @@ -53,84 +77,37 @@ function encodedSize(semanticTokens: ISemanticTokensDto): number { export function decodeSemanticTokensDto(buff: VSBuffer): ISemanticTokensDto { let offset = 0; const id = buff.readUInt32BE(offset); offset += 4; - const areasCount = buff.readUInt32BE(offset); offset += 4; - let areas: ISemanticTokensAreaDto[] = []; - for (let i = 0; i < areasCount; i++) { - offset = decodeArea(buff, offset, areas); + const type: EncodedSemanticTokensType = buff.readUInt8(offset); offset += 1; + if (type === EncodedSemanticTokensType.Full) { + const length = buff.readUInt32BE(offset); offset += 4; + const data = new Uint32Array(length); + for (let j = 0; j < length; j++) { + data[j] = buff.readUInt32BE(offset); offset += 4; + } + return { + id: id, + type: 'full', + data: data + }; + } + const deltaCount = buff.readUInt32BE(offset); offset += 4; + let deltas: { start: number; deleteCount: number; data?: Uint32Array; }[] = []; + for (let i = 0; i < deltaCount; i++) { + const start = buff.readUInt32BE(offset); offset += 4; + const deleteCount = buff.readUInt32BE(offset); offset += 4; + const length = buff.readUInt32BE(offset); offset += 4; + let data: Uint32Array | undefined; + if (length > 0) { + data = new Uint32Array(length); + for (let j = 0; j < length; j++) { + data[j] = buff.readUInt32BE(offset); offset += 4; + } + } + deltas[i] = { start, deleteCount, data }; } return { id: id, - areas: areas + type: 'delta', + deltas: deltas }; } - -function encodeArea(area: ISemanticTokensAreaDto, buff: VSBuffer, offset: number): number { - buff.writeUInt8(area.type === 'full' ? EncodedSemanticTokensAreaType.Full : EncodedSemanticTokensAreaType.Delta, offset); offset += 1; - buff.writeUInt32BE(area.line + 1, offset); offset += 4; - if (area.type === 'full') { - const tokens = area.data; - const tokenCount = (tokens.length / 5) | 0; - buff.writeUInt32BE(tokenCount, offset); offset += 4; - // here we are explicitly iterating an writing the ints again to ensure writing the desired endianness. - for (let i = 0; i < tokenCount; i++) { - const tokenOffset = 5 * i; - buff.writeUInt32BE(tokens[tokenOffset], offset); offset += 4; - buff.writeUInt32BE(tokens[tokenOffset + 1], offset); offset += 4; - buff.writeUInt32BE(tokens[tokenOffset + 2], offset); offset += 4; - buff.writeUInt32BE(tokens[tokenOffset + 3], offset); offset += 4; - buff.writeUInt32BE(tokens[tokenOffset + 4], offset); offset += 4; - } - // buff.set(VSBuffer.wrap(uint8), offset); offset += area.data.byteLength; - } else { - buff.writeUInt32BE(area.oldIndex, offset); offset += 4; - } - return offset; -} - -function encodedAreaSize(area: ISemanticTokensAreaDto): number { - let result = 0; - result += 1; // type - result += 4; // line - if (area.type === 'full') { - const tokens = area.data; - const tokenCount = (tokens.length / 5) | 0; - result += 4; // token count - result += tokenCount * 5 * 4; - return result; - } else { - result += 4; // old index - return result; - } -} - -function decodeArea(buff: VSBuffer, offset: number, areas: ISemanticTokensAreaDto[]): number { - const type: EncodedSemanticTokensAreaType = buff.readUInt8(offset); offset += 1; - const line = buff.readUInt32BE(offset); offset += 4; - if (type === EncodedSemanticTokensAreaType.Full) { - // here we are explicitly iterating and reading the ints again to ensure reading the desired endianness. - const tokenCount = buff.readUInt32BE(offset); offset += 4; - const data = new Uint32Array(5 * tokenCount); - for (let i = 0; i < tokenCount; i++) { - const destOffset = 5 * i; - data[destOffset] = buff.readUInt32BE(offset); offset += 4; - data[destOffset + 1] = buff.readUInt32BE(offset); offset += 4; - data[destOffset + 2] = buff.readUInt32BE(offset); offset += 4; - data[destOffset + 3] = buff.readUInt32BE(offset); offset += 4; - data[destOffset + 4] = buff.readUInt32BE(offset); offset += 4; - } - areas.push({ - type: 'full', - line: line, - data: data - }); - return offset; - } else { - const oldIndex = buff.readUInt32BE(offset); offset += 4; - areas.push({ - type: 'delta', - line: line, - oldIndex: oldIndex - }); - return offset; - } -} diff --git a/src/vs/workbench/test/electron-browser/api/semanticTokens.test.ts b/src/vs/workbench/test/electron-browser/api/semanticTokens.test.ts deleted file mode 100644 index 978888ef532..00000000000 --- a/src/vs/workbench/test/electron-browser/api/semanticTokens.test.ts +++ /dev/null @@ -1,343 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import { URI } from 'vs/base/common/uri'; -import * as types from 'vs/workbench/api/common/extHostTypes'; -import { TestRPCProtocol } from 'vs/workbench/test/electron-browser/api/testRPCProtocol'; -import { SemanticColoringAdapter, SemanticColoringConstants } from 'vs/workbench/api/common/extHostLanguageFeatures'; -import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; -import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; -import { ExtHostContext } from 'vs/workbench/api/common/extHost.protocol'; -import * as vscode from 'vscode'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { decodeSemanticTokensDto, ISemanticTokensDto } from 'vs/workbench/api/common/shared/semanticTokens'; -import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData'; - -suite('SemanticColoringAdapter', () => { - - const resource = URI.parse('foo:bar'); - const rpcProtocol = new TestRPCProtocol(); - - const initialText = [ - 'const enum E01 {}', - 'const enum E02 {}', - 'const enum E03 {}', - 'const enum E04 {}', - 'const enum E05 {}', - 'const enum E06 {}', - 'const enum E07 {}', - 'const enum E08 {}', - 'const enum E09 {}', - 'const enum E10 {}', - 'const enum E11 {}', - 'const enum E12 {}', - 'const enum E13 {}', - 'const enum E14 {}', - 'const enum E15 {}', - 'const enum E16 {}', - 'const enum E17 {}', - 'const enum E18 {}', - 'const enum E19 {}', - 'const enum E20 {}', - 'const enum E21 {}', - 'const enum E22 {}', - 'const enum E23 {}', - ].join('\n'); - - const extHostDocumentsAndEditors = new ExtHostDocumentsAndEditors(rpcProtocol); - extHostDocumentsAndEditors.$acceptDocumentsAndEditorsDelta({ - addedDocuments: [{ - isDirty: false, - versionId: 1, - modeId: 'javascript', - uri: resource, - lines: initialText.split(/\n/), - EOL: '\n', - }] - }); - const extHostDocuments = new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors); - rpcProtocol.set(ExtHostContext.ExtHostDocuments, extHostDocuments); - - const semanticTokensProvider = new class implements vscode.SemanticColoringProvider { - provideSemanticColoring(document: vscode.TextDocument, token: vscode.CancellationToken): types.SemanticColoring { - const lines = document.getText().split(/\r\n|\r|\n/g); - const tokens: number[] = []; - const pushToken = (line: number, startCharacter: number, endCharacter: number, type: number) => { - tokens.push(line); - tokens.push(startCharacter); - tokens.push(endCharacter); - tokens.push(type); - tokens.push(0); - }; - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - const m = line.match(/^(const enum )([\w\d]+) \{\}/); - if (m) { - pushToken(i, m[1].length, m[1].length + m[2].length, parseInt(m[2].substr(1))); - } - } - return new types.SemanticColoring([new types.SemanticColoringArea(0, new Uint32Array(tokens))]); - } - }; - - let adapter: SemanticColoringAdapter; - let doc: ExtHostDocumentData; - - setup(() => { - adapter = new SemanticColoringAdapter(extHostDocuments, semanticTokensProvider, 10, SemanticColoringConstants.DesiredMaxAreas, 5); - doc = extHostDocumentsAndEditors.getDocument(resource)!; - const docLineCount = doc.document.lineCount; - const allRange = { startLineNumber: 1, startColumn: 1, endLineNumber: docLineCount, endColumn: doc.document.lineAt(docLineCount - 1).text.length + 1 }; - doc.onEvents({ - versionId: 1, - eol: '\n', - changes: [{ - range: allRange, - rangeOffset: 0, - rangeLength: 0, - text: initialText - }] - }); - }); - - type SimpleTokensDto = { type: 'full'; line: number; tokens: number[]; } | { type: 'delta'; line: number; oldIndex: number }; - - function assertDTO(actual: ISemanticTokensDto, expected: SimpleTokensDto[]): void { - const simpleActual: SimpleTokensDto[] = actual.areas.map((area) => { - if (area.type === 'full') { - const tokenCount = (area.data.length / 5) | 0; - let tokens: number[] = []; - for (let i = 0; i < tokenCount; i++) { - tokens.push(area.data[5 * i]); - } - return { - type: 'full', - line: area.line, - tokens: tokens - }; - } - return { - type: 'delta', - line: area.line, - oldIndex: area.oldIndex - }; - }); - assert.deepEqual(simpleActual, expected); - } - - test('single area - breaks it up', async () => { - const dto = (await adapter.provideSemanticColoring(resource, 0, CancellationToken.None))!; - const result = decodeSemanticTokensDto(dto); - assertDTO(result, [ - { type: 'full', line: 1, tokens: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] }, - { type: 'full', line: 11, tokens: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] }, - { type: 'full', line: 21, tokens: [0, 1, 2] }, - ]); - }); - - test('single area - after a not important change', async () => { - const result1 = decodeSemanticTokensDto((await adapter.provideSemanticColoring(resource, 0, CancellationToken.None))!); - assertDTO(result1, [ - { type: 'full', line: 1, tokens: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] }, - { type: 'full', line: 11, tokens: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] }, - { type: 'full', line: 21, tokens: [0, 1, 2] }, - ]); - - doc.onEvents({ - versionId: 2, - eol: '\n', - changes: [{ - range: { startLineNumber: 2, startColumn: 18, endLineNumber: 2, endColumn: 18 }, - rangeOffset: 0, - rangeLength: 0, - text: '//' - }] - }); - - const result2 = decodeSemanticTokensDto((await adapter.provideSemanticColoring(resource, result1.id, CancellationToken.None))!); - assertDTO(result2, [ - { type: 'delta', line: 1, oldIndex: 0 }, - { type: 'delta', line: 11, oldIndex: 1 }, - { type: 'delta', line: 21, oldIndex: 2 }, - ]); - adapter.releaseSemanticColoring(result1.id); - }); - - test('single area - after a single removal in the first block', async () => { - const result1 = decodeSemanticTokensDto((await adapter.provideSemanticColoring(resource, 0, CancellationToken.None))!); - assertDTO(result1, [ - { type: 'full', line: 1, tokens: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] }, - { type: 'full', line: 11, tokens: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] }, - { type: 'full', line: 21, tokens: [0, 1, 2] }, - ]); - - doc.onEvents({ - versionId: 2, - eol: '\n', - changes: [{ - range: { startLineNumber: 2, startColumn: 1, endLineNumber: 2, endColumn: 1 }, - rangeOffset: 0, - rangeLength: 0, - text: '//' - }] - }); - - const result2 = decodeSemanticTokensDto((await adapter.provideSemanticColoring(resource, result1.id, CancellationToken.None))!); - assertDTO(result2, [ - { type: 'full', line: 1, tokens: [0, 2, 3, 4, 5, 6, 7, 8, 9] }, - { type: 'delta', line: 11, oldIndex: 1 }, - { type: 'delta', line: 21, oldIndex: 2 }, - ]); - adapter.releaseSemanticColoring(result1.id); - }); - - test('single area - after a not important change', async () => { - const result1 = decodeSemanticTokensDto((await adapter.provideSemanticColoring(resource, 0, CancellationToken.None))!); - assertDTO(result1, [ - { type: 'full', line: 1, tokens: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] }, - { type: 'full', line: 11, tokens: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] }, - { type: 'full', line: 21, tokens: [0, 1, 2] }, - ]); - - doc.onEvents({ - versionId: 2, - eol: '\n', - changes: [{ - range: { startLineNumber: 2, startColumn: 18, endLineNumber: 2, endColumn: 18 }, - rangeOffset: 0, - rangeLength: 0, - text: '//' - }] - }); - - const result2 = decodeSemanticTokensDto((await adapter.provideSemanticColoring(resource, result1.id, CancellationToken.None))!); - assertDTO(result2, [ - { type: 'delta', line: 1, oldIndex: 0 }, - { type: 'delta', line: 11, oldIndex: 1 }, - { type: 'delta', line: 21, oldIndex: 2 }, - ]); - adapter.releaseSemanticColoring(result1.id); - }); - - test('single area - after a down shift of all the blocks', async () => { - const result1 = decodeSemanticTokensDto((await adapter.provideSemanticColoring(resource, 0, CancellationToken.None))!); - assertDTO(result1, [ - { type: 'full', line: 1, tokens: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] }, - { type: 'full', line: 11, tokens: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] }, - { type: 'full', line: 21, tokens: [0, 1, 2] }, - ]); - - doc.onEvents({ - versionId: 2, - eol: '\n', - changes: [{ - range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }, - rangeOffset: 0, - rangeLength: 0, - text: '\n' - }] - }); - - const result2 = decodeSemanticTokensDto((await adapter.provideSemanticColoring(resource, result1.id, CancellationToken.None))!); - assertDTO(result2, [ - { type: 'delta', line: 2, oldIndex: 0 }, - { type: 'delta', line: 12, oldIndex: 1 }, - { type: 'delta', line: 22, oldIndex: 2 }, - ]); - adapter.releaseSemanticColoring(result1.id); - }); - - test('single area - after a single removal in the last block', async () => { - const result1 = decodeSemanticTokensDto((await adapter.provideSemanticColoring(resource, 0, CancellationToken.None))!); - assertDTO(result1, [ - { type: 'full', line: 1, tokens: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] }, - { type: 'full', line: 11, tokens: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] }, - { type: 'full', line: 21, tokens: [0, 1, 2] }, - ]); - - doc.onEvents({ - versionId: 2, - eol: '\n', - changes: [{ - range: { startLineNumber: 22, startColumn: 1, endLineNumber: 22, endColumn: 1 }, - rangeOffset: 0, - rangeLength: 0, - text: '//' - }] - }); - - const result2 = decodeSemanticTokensDto((await adapter.provideSemanticColoring(resource, result1.id, CancellationToken.None))!); - assertDTO(result2, [ - { type: 'delta', line: 1, oldIndex: 0 }, - { type: 'delta', line: 11, oldIndex: 1 }, - { type: 'full', line: 21, tokens: [0, 2] }, - ]); - adapter.releaseSemanticColoring(result1.id); - }); - - test('single area - after a single addition in the first block', async () => { - const result1 = decodeSemanticTokensDto((await adapter.provideSemanticColoring(resource, 0, CancellationToken.None))!); - assertDTO(result1, [ - { type: 'full', line: 1, tokens: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] }, - { type: 'full', line: 11, tokens: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] }, - { type: 'full', line: 21, tokens: [0, 1, 2] }, - ]); - - doc.onEvents({ - versionId: 2, - eol: '\n', - changes: [{ - range: { startLineNumber: 2, startColumn: 1, endLineNumber: 2, endColumn: 1 }, - rangeOffset: 0, - rangeLength: 0, - text: 'const enum E00 {}\n' - }] - }); - - const result2 = decodeSemanticTokensDto((await adapter.provideSemanticColoring(resource, result1.id, CancellationToken.None))!); - assertDTO(result2, [ - { type: 'full', line: 1, tokens: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }, - { type: 'delta', line: 12, oldIndex: 1 }, - { type: 'delta', line: 22, oldIndex: 2 }, - ]); - adapter.releaseSemanticColoring(result1.id); - }); - - test('going from empty to 1 semantic token', async () => { - doc.onEvents({ - versionId: 2, - eol: '\n', - changes: [{ - range: { startLineNumber: 1, startColumn: 1, endLineNumber: 23, endColumn: 18 }, - rangeOffset: 0, - rangeLength: 0, - text: '' - }] - }); - - const result1 = decodeSemanticTokensDto((await adapter.provideSemanticColoring(resource, 0, CancellationToken.None))!); - assertDTO(result1, [ - { type: 'full', line: 1, tokens: [] }, - ]); - - doc.onEvents({ - versionId: 3, - eol: '\n', - changes: [{ - range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }, - rangeOffset: 0, - rangeLength: 0, - text: 'const enum E01 {}\n' - }] - }); - - const result2 = decodeSemanticTokensDto((await adapter.provideSemanticColoring(resource, result1.id, CancellationToken.None))!); - assertDTO(result2, [ - { type: 'full', line: 1, tokens: [0] } - ]); - adapter.releaseSemanticColoring(result1.id); - }); -}); From d832f86b04d6654f066c6489221b33d6c428ab62 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 2 Dec 2019 10:51:29 +0100 Subject: [PATCH 058/255] remoteAuthority setting inside a .code-workspace file lacks description. Fixes #83657 --- src/vs/workbench/api/common/configurationExtensionPoint.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/api/common/configurationExtensionPoint.ts b/src/vs/workbench/api/common/configurationExtensionPoint.ts index 65e5d6cf070..797cd6d8f9a 100644 --- a/src/vs/workbench/api/common/configurationExtensionPoint.ts +++ b/src/vs/workbench/api/common/configurationExtensionPoint.ts @@ -309,7 +309,9 @@ jsonRegistry.registerSchema('vscode://schemas/workspaceConfig', { $ref: 'vscode://schemas/extensions' }, 'remoteAuthority': { - type: 'string' + type: 'string', + doNotSuggest: true, + description: nls.localize('workspaceConfig.remoteAuthority', "The remote server where the workspace is located. Only used by unsaved remote workspaces."), } }, additionalProperties: false, From 46d8590621643e8114d18f17328d77f8ac0cf422 Mon Sep 17 00:00:00 2001 From: isidor Date: Mon, 2 Dec 2019 11:10:39 +0100 Subject: [PATCH 059/255] debug.InlineBreakpointCandidates -> debug.showInlineBreakpointCandidates #83018 --- .../contrib/debug/browser/breakpointEditorContribution.ts | 4 ++-- src/vs/workbench/contrib/debug/browser/debug.contribution.ts | 4 ++-- src/vs/workbench/contrib/debug/common/debug.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts index d45e0ec4d02..5374107fe82 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts @@ -273,7 +273,7 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { })); this.toDispose.push(this.editor.onDidChangeModelDecorations(() => this.onModelDecorationsChanged())); this.toDispose.push(this.configurationService.onDidChangeConfiguration(async (e) => { - if (e.affectsConfiguration('debug.showBreakpointsInOverviewRuler') || e.affectsConfiguration('debug.inlineBreakpointCandidates')) { + if (e.affectsConfiguration('debug.showBreakpointsInOverviewRuler') || e.affectsConfiguration('debug.showInlineBreakpointCandidates')) { await this.setDecorations(); } })); @@ -425,7 +425,7 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { } // Set breakpoint candidate decorations - const desiredCandidateDecorations = debugSettings.inlineBreakpointCandidates ? await createCandidateDecorations(this.editor.getModel(), this.breakpointDecorations, this.debugService) : []; + const desiredCandidateDecorations = debugSettings.showInlineBreakpointCandidates ? await createCandidateDecorations(this.editor.getModel(), this.breakpointDecorations, this.debugService) : []; const candidateDecorationIds = this.editor.deltaDecorations(this.candidateDecorations.map(c => c.decorationId), desiredCandidateDecorations); this.candidateDecorations.forEach(candidate => { candidate.inlineWidget.dispose(); diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index 4aa9176194b..a32eb210b31 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -272,9 +272,9 @@ configurationRegistry.registerConfiguration({ description: nls.localize({ comment: ['This is the description for a setting'], key: 'showBreakpointsInOverviewRuler' }, "Controls whether breakpoints should be shown in the overview ruler."), default: false }, - 'debug.inlineBreakpointCandidates': { + 'debug.showInlineBreakpointCandidates': { type: 'boolean', - description: nls.localize({ comment: ['This is the description for a setting'], key: 'inlineBreakpointCandidates' }, "Controls whether inline breakpoints candidate decorations should be shown in the editor while debugging."), + description: nls.localize({ comment: ['This is the description for a setting'], key: 'showInlineBreakpointCandidates' }, "Controls whether inline breakpoints candidate decorations should be shown in the editor while debugging."), default: true } } diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index b7795ef5a6e..9b87af8cbe4 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -470,7 +470,7 @@ export interface IDebugConfiguration { focusWindowOnBreak: boolean; onTaskErrors: 'debugAnyway' | 'showErrors' | 'prompt'; showBreakpointsInOverviewRuler: boolean; - inlineBreakpointCandidates: boolean; + showInlineBreakpointCandidates: boolean; } export interface IGlobalConfig { From 4b0a066b83e08d2170877ac8f09f080339a3773c Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 2 Dec 2019 11:15:31 +0100 Subject: [PATCH 060/255] - Get formatting options using text model - Send only bindings to resolve --- .../sharedProcess/sharedProcessMain.ts | 6 +- .../platform/userDataSync/common/content.ts | 17 +- .../userDataSync/common/keybindingsMerge.ts | 33 +- .../userDataSync/common/keybindingsSync.ts | 8 +- .../userDataSync/common/keybindingsSyncIpc.ts | 21 +- .../userDataSync/common/userDataSync.ts | 10 +- .../test/common/keybindingsMerge.test.ts | 611 +++++++++--------- .../userDataSync.contribution.ts | 8 +- .../userDataSync/common/keybindingsMerge.ts | 32 - .../userDataSync/common/userDataSyncUtil.ts | 40 ++ src/vs/workbench/workbench.common.main.ts | 2 +- 11 files changed, 408 insertions(+), 380 deletions(-) delete mode 100644 src/vs/workbench/services/userDataSync/common/keybindingsMerge.ts create mode 100644 src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 822ba16de3c..27469c14739 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -50,7 +50,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { DiskFileSystemProvider } from 'vs/platform/files/electron-browser/diskFileSystemProvider'; import { Schemas } from 'vs/base/common/network'; import { IProductService } from 'vs/platform/product/common/productService'; -import { IUserDataSyncService, IUserDataSyncStoreService, ISettingsMergeService, registerConfiguration, IUserDataSyncLogService, IUserKeybindingsResolverService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, IUserDataSyncStoreService, ISettingsMergeService, registerConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncService, UserDataAutoSync } from 'vs/platform/userDataSync/common/userDataSyncService'; import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; import { UserDataSyncChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc'; @@ -63,7 +63,7 @@ import { AuthTokenService } from 'vs/platform/auth/electron-browser/authTokenSer import { AuthTokenChannel } from 'vs/platform/auth/common/authTokenIpc'; import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; import { KeytarCredentialsService } from 'vs/platform/credentials/node/credentialsService'; -import { UserKeybindingsResolverServiceClient } from 'vs/platform/userDataSync/common/keybindingsSyncIpc'; +import { UserDataSyncUtilServiceClient } from 'vs/platform/userDataSync/common/keybindingsSyncIpc'; export interface ISharedProcessConfiguration { readonly machineId: string; @@ -187,7 +187,7 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat services.set(IUserDataSyncLogService, new SyncDescriptor(UserDataSyncLogService)); const settingsMergeChannel = server.getChannel('settingsMerge', activeWindowRouter); services.set(ISettingsMergeService, new SettingsMergeChannelClient(settingsMergeChannel)); - services.set(IUserKeybindingsResolverService, new UserKeybindingsResolverServiceClient(server.getChannel('userKeybindingsResolver', activeWindowRouter))); + services.set(IUserDataSyncUtilService, new UserDataSyncUtilServiceClient(server.getChannel('userDataSyncUtil', activeWindowRouter))); services.set(IUserDataSyncStoreService, new SyncDescriptor(UserDataSyncStoreService)); services.set(IUserDataSyncService, new SyncDescriptor(UserDataSyncService)); registerConfiguration(); diff --git a/src/vs/platform/userDataSync/common/content.ts b/src/vs/platform/userDataSync/common/content.ts index f50d0c0bfb1..5dd3d97a427 100644 --- a/src/vs/platform/userDataSync/common/content.ts +++ b/src/vs/platform/userDataSync/common/content.ts @@ -3,13 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { OperatingSystem, OS } from 'vs/base/common/platform'; import { JSONPath } from 'vs/base/common/json'; import { setProperty } from 'vs/base/common/jsonEdit'; +import { FormattingOptions } from 'vs/base/common/jsonFormatter'; -export function edit(content: string, eol: string, originalPath: JSONPath, value: any): string { - const edit = setProperty(content, originalPath, value, { tabSize: 4, insertSpaces: false, eol })[0]; +export function edit(content: string, originalPath: JSONPath, value: any, formattingOptions: FormattingOptions): string { + const edit = setProperty(content, originalPath, value, formattingOptions)[0]; if (edit) { content = content.substring(0, edit.offset) + edit.content + content.substring(edit.offset + edit.length); } @@ -51,14 +51,3 @@ export function getLineEndOffset(content: string, eol: string, atOffset: number) } return content.length - 1; } - -export function getEol(content: string): string { - if (content.indexOf('\r\n') !== -1) { - return '\r\n'; - } - if (content.indexOf('\n') !== -1) { - return '\n'; - } - return OS === OperatingSystem.Linux || OS === OperatingSystem.Macintosh ? '\n' : '\r\n'; -} - diff --git a/src/vs/platform/userDataSync/common/keybindingsMerge.ts b/src/vs/platform/userDataSync/common/keybindingsMerge.ts index c41df367b34..3c9910a3095 100644 --- a/src/vs/platform/userDataSync/common/keybindingsMerge.ts +++ b/src/vs/platform/userDataSync/common/keybindingsMerge.ts @@ -11,6 +11,8 @@ import { firstIndex as findFirstIndex, equals } from 'vs/base/common/arrays'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import * as contentUtil from 'vs/platform/userDataSync/common/content'; import { IStringDictionary } from 'vs/base/common/collections'; +import { FormattingOptions } from 'vs/base/common/jsonFormatter'; +import { IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; interface ICompareResult { added: Set; @@ -27,11 +29,13 @@ interface IMergeResult { conflicts: Set; } -export function merge(localContent: string, remoteContent: string, baseContent: string | null, normalizedKeys: IStringDictionary): { mergeContent: string, hasChanges: boolean, hasConflicts: boolean } { +export async function merge(localContent: string, remoteContent: string, baseContent: string | null, formattingOptions: FormattingOptions, userDataSyncUtilService: IUserDataSyncUtilService): Promise<{ mergeContent: string, hasChanges: boolean, hasConflicts: boolean }> { const local = parse(localContent); const remote = parse(remoteContent); const base = baseContent ? parse(baseContent) : null; + const userbindings: string[] = [...local, ...remote, ...(base || [])].map(keybinding => keybinding.key); + const normalizedKeys = await userDataSyncUtilService.resolveUserBindings(userbindings); let keybindingsMergeResult = computeMergeResultByKeybinding(local, remote, base, normalizedKeys); if (!keybindingsMergeResult.hasLocalForwarded && !keybindingsMergeResult.hasRemoteForwarded) { @@ -57,7 +61,6 @@ export function merge(localContent: string, remoteContent: string, baseContent: const baseToRemoteByCommand = baseByCommand ? compareByCommand(baseByCommand, remoteByCommand, normalizedKeys) : { added: keys(remoteByCommand).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; const commandsMergeResult = computeMergeResult(localToRemoteByCommand, baseToLocalByCommand, baseToRemoteByCommand); - const eol = contentUtil.getEol(localContent); let mergeContent = localContent; // Removed commands in Remote @@ -65,7 +68,7 @@ export function merge(localContent: string, remoteContent: string, baseContent: if (commandsMergeResult.conflicts.has(command)) { continue; } - mergeContent = removeKeybindings(mergeContent, eol, command); + mergeContent = removeKeybindings(mergeContent, command, formattingOptions); } // Added commands in remote @@ -79,7 +82,7 @@ export function merge(localContent: string, remoteContent: string, baseContent: commandsMergeResult.conflicts.add(command); continue; } - mergeContent = addKeybindings(mergeContent, eol, keybindings); + mergeContent = addKeybindings(mergeContent, keybindings, formattingOptions); } // Updated commands in Remote @@ -93,16 +96,16 @@ export function merge(localContent: string, remoteContent: string, baseContent: commandsMergeResult.conflicts.add(command); continue; } - mergeContent = updateKeybindings(mergeContent, eol, command, keybindings); + mergeContent = updateKeybindings(mergeContent, command, keybindings, formattingOptions); } const hasConflicts = commandsMergeResult.conflicts.size > 0; if (hasConflicts) { - mergeContent = `<<<<<<< local${eol}` + mergeContent = `<<<<<<< local${formattingOptions.eol}` + mergeContent - + `${eol}=======${eol}` + + `${formattingOptions.eol}=======${formattingOptions.eol}` + remoteContent - + `${eol}>>>>>>> remote`; + + `${formattingOptions.eol}>>>>>>> remote`; } return { mergeContent, hasChanges: true, hasConflicts }; @@ -330,35 +333,35 @@ function isSameKeybinding(a: IUserFriendlyKeybinding, b: IUserFriendlyKeybinding return true; } -function addKeybindings(content: string, eol: string, keybindings: IUserFriendlyKeybinding[]): string { +function addKeybindings(content: string, keybindings: IUserFriendlyKeybinding[], formattingOptions: FormattingOptions): string { for (const keybinding of keybindings) { - content = contentUtil.edit(content, eol, [-1], keybinding); + content = contentUtil.edit(content, [-1], keybinding, formattingOptions); } return content; } -function removeKeybindings(content: string, eol: string, command: string): string { +function removeKeybindings(content: string, command: string, formattingOptions: FormattingOptions): string { const keybindings = parse(content); for (let index = keybindings.length - 1; index >= 0; index--) { if (keybindings[index].command === command || keybindings[index].command === `-${command}`) { - content = contentUtil.edit(content, eol, [index], undefined); + content = contentUtil.edit(content, [index], undefined, formattingOptions); } } return content; } -function updateKeybindings(content: string, eol: string, command: string, keybindings: IUserFriendlyKeybinding[]): string { +function updateKeybindings(content: string, command: string, keybindings: IUserFriendlyKeybinding[], formattingOptions: FormattingOptions): string { const allKeybindings = parse(content); const location = findFirstIndex(allKeybindings, keybinding => keybinding.command === command || keybinding.command === `-${command}`); // Remove all entries with this command for (let index = allKeybindings.length - 1; index >= 0; index--) { if (allKeybindings[index].command === command || allKeybindings[index].command === `-${command}`) { - content = contentUtil.edit(content, eol, [index], undefined); + content = contentUtil.edit(content, [index], undefined, formattingOptions); } } // add all entries at the same location where the entry with this command was located. for (let index = keybindings.length - 1; index >= 0; index--) { - content = contentUtil.edit(content, eol, [location], keybindings[index]); + content = contentUtil.edit(content, [location], keybindings[index], formattingOptions); } return content; } diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index 20779f8687d..f278643a849 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -5,7 +5,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IFileService, FileSystemProviderErrorCode, FileSystemProviderError, IFileContent } from 'vs/platform/files/common/files'; -import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, ISynchroniser, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserKeybindingsResolverService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, ISynchroniser, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; import { merge } from 'vs/platform/userDataSync/common/keybindingsMerge'; import { VSBuffer } from 'vs/base/common/buffer'; import { parse, ParseError } from 'vs/base/common/json'; @@ -58,7 +58,7 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser @IConfigurationService private readonly configurationService: IConfigurationService, @IFileService private readonly fileService: IFileService, @IEnvironmentService private readonly environmentService: IEnvironmentService, - @IUserKeybindingsResolverService private readonly userKeybindingsResolverService: IUserKeybindingsResolverService, + @IUserDataSyncUtilService private readonly userDataSyncUtilService: IUserDataSyncUtilService, ) { super(); this.lastSyncKeybindingsResource = joinPath(this.environmentService.userRoamingDataHome, '.lastSyncKeybindings.json'); @@ -234,8 +234,8 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser || lastSyncContent !== remoteContent // Remote has forwarded ) { this.logService.trace('Keybindings: Merging remote keybindings with local keybindings...'); - const keys = await this.userKeybindingsResolverService.resolveUserKeybindings(localContent, remoteContent, lastSyncContent); - const result = merge(localContent, remoteContent, lastSyncContent, keys); + const formattingOptions = await this.userDataSyncUtilService.resolveFormattingOptions(this.environmentService.keybindingsResource); + const result = await merge(localContent, remoteContent, lastSyncContent, formattingOptions, this.userDataSyncUtilService); // Sync only if there are changes if (result.hasChanges) { hasLocalChanged = result.mergeContent !== localContent; diff --git a/src/vs/platform/userDataSync/common/keybindingsSyncIpc.ts b/src/vs/platform/userDataSync/common/keybindingsSyncIpc.ts index c4c852604a9..f23b3c90c0b 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSyncIpc.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSyncIpc.ts @@ -5,12 +5,14 @@ import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { Event } from 'vs/base/common/event'; -import { IUserKeybindingsResolverService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; import { IStringDictionary } from 'vs/base/common/collections'; +import { URI } from 'vs/base/common/uri'; +import { FormattingOptions } from 'vs/base/common/jsonFormatter'; -export class UserKeybindingsResolverServiceChannel implements IServerChannel { +export class UserDataSycnUtilServiceChannel implements IServerChannel { - constructor(private readonly service: IUserKeybindingsResolverService) { } + constructor(private readonly service: IUserDataSyncUtilService) { } listen(_: unknown, event: string): Event { throw new Error(`Event not found: ${event}`); @@ -18,21 +20,26 @@ export class UserKeybindingsResolverServiceChannel implements IServerChannel { call(context: any, command: string, args?: any): Promise { switch (command) { - case 'resolveUserKeybindings': return this.service.resolveUserKeybindings(args[0], args[1], args[2]); + case 'resolveUserKeybindings': return this.service.resolveUserBindings(args[0]); + case 'resolveFormattingOptions': return this.service.resolveFormattingOptions(URI.revive(args[0])); } throw new Error('Invalid call'); } } -export class UserKeybindingsResolverServiceClient implements IUserKeybindingsResolverService { +export class UserDataSyncUtilServiceClient implements IUserDataSyncUtilService { _serviceBrand: undefined; constructor(private readonly channel: IChannel) { } - async resolveUserKeybindings(localContent: string, remoteContent: string, baseContent: string | null): Promise> { - return this.channel.call('resolveUserKeybindings', [localContent, remoteContent, baseContent]); + async resolveUserBindings(userbindings: string[]): Promise> { + return this.channel.call('resolveUserKeybindings', [userbindings]); + } + + async resolveFormattingOptions(file: URI): Promise { + return this.channel.call('resolveFormattingOptions', [file]); } } diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index a809bdf74ca..bc76b41f9b0 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -16,6 +16,8 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { ILogService } from 'vs/platform/log/common/log'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IStringDictionary } from 'vs/base/common/collections'; +import { FormattingOptions } from 'vs/base/common/jsonFormatter'; +import { URI } from 'vs/base/common/uri'; const CONFIGURATION_SYNC_STORE_KEY = 'configurationSync.store'; @@ -187,13 +189,15 @@ export interface ISettingsMergeService { } -export const IUserKeybindingsResolverService = createDecorator('IUserKeybindingsResolverService'); +export const IUserDataSyncUtilService = createDecorator('IUserDataSyncUtilService'); -export interface IUserKeybindingsResolverService { +export interface IUserDataSyncUtilService { _serviceBrand: undefined; - resolveUserKeybindings(localContent: string, remoteContent: string, baseContent: string | null): Promise>; + resolveUserBindings(userbindings: string[]): Promise>; + + resolveFormattingOptions(resource: URI): Promise; } diff --git a/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts b/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts index 4d81421ed62..d6cce88a7bd 100644 --- a/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts +++ b/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts @@ -6,15 +6,17 @@ import * as assert from 'assert'; import { merge } from 'vs/platform/userDataSync/common/keybindingsMerge'; import { IStringDictionary } from 'vs/base/common/collections'; -import { IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding'; -import { parse } from 'vs/base/common/json'; +import { OperatingSystem, OS } from 'vs/base/common/platform'; +import { IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; +import { FormattingOptions } from 'vs/base/common/jsonFormatter'; +import { URI } from 'vs/base/common/uri'; suite('KeybindingsMerge - No Conflicts', () => { test('merge when local and remote are same with one entry', async () => { const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const actual = mergeKeybindings(localContent, remoteContent, null); + const actual = await mergeKeybindings(localContent, remoteContent, null); assert.ok(!actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); @@ -23,7 +25,7 @@ suite('KeybindingsMerge - No Conflicts', () => { test('merge when local and remote are same with similar when contexts', async () => { const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: '!editorReadonly && editorTextFocus' }]); - const actual = mergeKeybindings(localContent, remoteContent, null); + const actual = await mergeKeybindings(localContent, remoteContent, null); assert.ok(!actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); @@ -38,7 +40,7 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+a', command: 'a', when: 'editorTextFocus' }, { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' } ]); - const actual = mergeKeybindings(localContent, remoteContent, null); + const actual = await mergeKeybindings(localContent, remoteContent, null); assert.ok(!actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); @@ -55,7 +57,7 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+d', command: '-a' }, { key: 'cmd+c', command: 'b', args: { text: '`' } } ]); - const actual = mergeKeybindings(localContent, remoteContent, null); + const actual = await mergeKeybindings(localContent, remoteContent, null); assert.ok(!actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); @@ -76,7 +78,7 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+d', command: '-a' }, { key: 'cmd+c', command: 'b', args: { text: '`' } } ]); - const actual = mergeKeybindings(localContent, remoteContent, baseContent); + const actual = await mergeKeybindings(localContent, remoteContent, baseContent); assert.ok(!actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); @@ -93,7 +95,7 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, ]); - const actual = mergeKeybindings(localContent, remoteContent, null); + const actual = await mergeKeybindings(localContent, remoteContent, null); assert.ok(!actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); @@ -110,7 +112,7 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'cmd+c', command: 'b', args: { text: '`' } }, ]); - const actual = mergeKeybindings(localContent, remoteContent, null); + const actual = await mergeKeybindings(localContent, remoteContent, null); assert.ok(!actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); @@ -126,7 +128,7 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+d', command: '-a' }, { key: 'cmd+c', command: 'b', args: { text: '`' } }, ]); - const actual = mergeKeybindings(localContent, remoteContent, null); + const actual = await mergeKeybindings(localContent, remoteContent, null); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, remoteContent); @@ -143,7 +145,7 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'cmd+c', command: 'b', args: { text: '`' } }, { key: 'cmd+d', command: 'c' }, ]); - const actual = mergeKeybindings(localContent, remoteContent, null); + const actual = await mergeKeybindings(localContent, remoteContent, null); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, remoteContent); @@ -160,7 +162,7 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'cmd+c', command: 'b', args: { text: '`' } }, { key: 'cmd+d', command: 'c' }, ]); - const actual = mergeKeybindings(localContent, remoteContent, localContent); + const actual = await mergeKeybindings(localContent, remoteContent, localContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, remoteContent); @@ -176,7 +178,7 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, ]); - const actual = mergeKeybindings(localContent, remoteContent, localContent); + const actual = await mergeKeybindings(localContent, remoteContent, localContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, remoteContent); @@ -190,7 +192,7 @@ suite('KeybindingsMerge - No Conflicts', () => { const remoteContent = stringify([ { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, ]); - const actual = mergeKeybindings(localContent, remoteContent, localContent); + const actual = await mergeKeybindings(localContent, remoteContent, localContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, remoteContent); @@ -203,7 +205,7 @@ suite('KeybindingsMerge - No Conflicts', () => { const remoteContent = stringify([ { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, ]); - const actual = mergeKeybindings(localContent, remoteContent, localContent); + const actual = await mergeKeybindings(localContent, remoteContent, localContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, remoteContent); @@ -222,7 +224,7 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+d', command: 'b' }, { key: 'cmd+d', command: 'a' }, ]); - const actual = mergeKeybindings(localContent, remoteContent, localContent); + const actual = await mergeKeybindings(localContent, remoteContent, localContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, remoteContent); @@ -245,7 +247,7 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'cmd+d', command: 'c', when: 'context1' }, { key: 'cmd+c', command: '-c' }, ]); - const actual = mergeKeybindings(localContent, remoteContent, localContent); + const actual = await mergeKeybindings(localContent, remoteContent, localContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, remoteContent); @@ -261,7 +263,7 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, ]); - const actual = mergeKeybindings(localContent, remoteContent, null); + const actual = await mergeKeybindings(localContent, remoteContent, null); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); @@ -278,7 +280,7 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, ]); - const actual = mergeKeybindings(localContent, remoteContent, null); + const actual = await mergeKeybindings(localContent, remoteContent, null); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); @@ -295,7 +297,7 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, ]); - const actual = mergeKeybindings(localContent, remoteContent, remoteContent); + const actual = await mergeKeybindings(localContent, remoteContent, remoteContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); @@ -311,7 +313,7 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+d', command: '-a' }, { key: 'cmd+c', command: 'b', args: { text: '`' } }, ]); - const actual = mergeKeybindings(localContent, remoteContent, remoteContent); + const actual = await mergeKeybindings(localContent, remoteContent, remoteContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); @@ -325,7 +327,7 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+d', command: '-a' }, ]); - const actual = mergeKeybindings(localContent, remoteContent, remoteContent); + const actual = await mergeKeybindings(localContent, remoteContent, remoteContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); @@ -338,7 +340,7 @@ suite('KeybindingsMerge - No Conflicts', () => { const remoteContent = stringify([ { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, ]); - const actual = mergeKeybindings(localContent, remoteContent, remoteContent); + const actual = await mergeKeybindings(localContent, remoteContent, remoteContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); @@ -357,7 +359,7 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+d', command: 'b' }, { key: 'cmd+d', command: 'a' }, ]); - const actual = mergeKeybindings(localContent, remoteContent, remoteContent); + const actual = await mergeKeybindings(localContent, remoteContent, remoteContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, localContent); @@ -389,7 +391,7 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'cmd+d', command: 'c', when: 'context1' }, { key: 'cmd+c', command: '-c' }, ]); - const actual = mergeKeybindings(localContent, remoteContent, remoteContent); + const actual = await mergeKeybindings(localContent, remoteContent, remoteContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, expected); @@ -432,7 +434,7 @@ suite('KeybindingsMerge - No Conflicts', () => { ]); //'[\n\t{\n\t\t"key": "alt+d",\n\t\t"command": "-f"\n\t},\n\t{\n\t\t"key": "cmd+d",\n\t\t"command": "d"\n\t},\n\t{\n\t\t"key": "cmd+c",\n\t\t"command": "-c"\n\t},\n\t{\n\t\t"key": "cmd+d",\n\t\t"command": "c",\n\t\t"when": "context1"\n\t},\n\t{\n\t\t"key": "alt+a",\n\t\t"command": "f"\n\t},\n\t{\n\t\t"key": "alt+e",\n\t\t"command": "e"\n\t},\n\t{\n\t\t"key": "alt+g",\n\t\t"command": "g",\n\t\t"when": "context2"\n\t}\n]' //'[\n\t{\n\t\t"key": "alt+d",\n\t\t"command": "-f"\n\t},\n\t{\n\t\t"key": "cmd+d",\n\t\t"command": "d"\n\t},\n\t{\n\t\t"key": "cmd+c",\n\t\t"command": "-c"\n\t},\n\t{\n\t\t"key": "alt+c",\n\t\t"command": "c",\n\t\t"when": "context1"\n\t},\n\t{\n\t\t"key": "alt+a",\n\t\t"command": "f"\n\t},\n\t{\n\t\t"key": "alt+e",\n\t\t"command": "e"\n\t},\n\t{\n\t\t"key": "alt+g",\n\t\t"command": "g",\n\t\t"when": "context2"\n\t}\n]' - const actual = mergeKeybindings(localContent, remoteContent, baseContent); + const actual = await mergeKeybindings(localContent, remoteContent, baseContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); assert.equal(actual.mergeContent, expected); @@ -440,282 +442,297 @@ suite('KeybindingsMerge - No Conflicts', () => { }); -suite.skip('KeybindingsMerge - Conflicts', () => { +if (OS !== OperatingSystem.Windows) { + + suite('KeybindingsMerge - Conflicts', () => { + + test('merge when local and remote with one entry but different value', async () => { + const localContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const actual = await mergeKeybindings(localContent, remoteContent, null); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local + [ + { + "key": "alt+d", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + } + ] + ======= + [ + { + "key": "alt+c", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + } + ] + >>>>>>> remote`); + }); + + test('merge when local and remote with different keybinding', async () => { + const localContent = stringify([ + { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+a', command: '-a', when: 'editorTextFocus && !editorReadonly' } + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+a', command: '-a', when: 'editorTextFocus && !editorReadonly' } + ]); + const actual = await mergeKeybindings(localContent, remoteContent, null); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local + [ + { + "key": "alt+d", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + }, + { + "key": "alt+a", + "command": "-a", + "when": "editorTextFocus && !editorReadonly" + } + ] + ======= + [ + { + "key": "alt+c", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + }, + { + "key": "alt+a", + "command": "-a", + "when": "editorTextFocus && !editorReadonly" + } + ] + >>>>>>> remote`); + }); + + test('merge when the entry is removed in local but updated in remote', async () => { + const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const localContent = stringify([]); + const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const actual = await mergeKeybindings(localContent, remoteContent, baseContent); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local + [] + ======= + [ + { + "key": "alt+c", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + } + ] + >>>>>>> remote`); + }); + + test('merge when the entry is removed in local but updated in remote and a new entry is added in local', async () => { + const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const localContent = stringify([{ key: 'alt+b', command: 'b' }]); + const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const actual = await mergeKeybindings(localContent, remoteContent, baseContent); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local + [ + { + "key": "alt+b", + "command": "b" + } + ] + ======= + [ + { + "key": "alt+c", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + } + ] + >>>>>>> remote`); + }); + + test('merge when the entry is removed in remote but updated in local', async () => { + const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const remoteContent = stringify([]); + const actual = await mergeKeybindings(localContent, remoteContent, baseContent); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local + [ + { + "key": "alt+c", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + } + ] + ======= + [] + >>>>>>> remote`); + }); + + test('merge when the entry is removed in remote but updated in local and a new entry is added in remote', async () => { + const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const remoteContent = stringify([{ key: 'alt+b', command: 'b' }]); + const actual = await mergeKeybindings(localContent, remoteContent, baseContent); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local + [ + { + "key": "alt+c", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + }, + { + "key": "alt+b", + "command": "b" + } + ] + ======= + [ + { + "key": "alt+b", + "command": "b" + } + ] + >>>>>>> remote`); + }); + + test('merge when local and remote has moved forwareded with conflicts', async () => { + const baseContent = stringify([ + { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+c', command: '-a' }, + { key: 'cmd+e', command: 'd' }, + { key: 'alt+a', command: 'f' }, + { key: 'alt+d', command: '-f' }, + { key: 'cmd+d', command: 'c', when: 'context1' }, + { key: 'cmd+c', command: '-c' }, + ]); + const localContent = stringify([ + { key: 'alt+d', command: '-f' }, + { key: 'cmd+e', command: 'd' }, + { key: 'cmd+c', command: '-c' }, + { key: 'cmd+d', command: 'c', when: 'context1' }, + { key: 'alt+a', command: 'f' }, + { key: 'alt+e', command: 'e' }, + ]); + const remoteContent = stringify([ + { key: 'alt+a', command: 'f' }, + { key: 'cmd+c', command: '-c' }, + { key: 'cmd+d', command: 'd' }, + { key: 'alt+d', command: '-f' }, + { key: 'alt+c', command: 'c', when: 'context1' }, + { key: 'alt+g', command: 'g', when: 'context2' }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, baseContent); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local + [ + { + "key": "alt+d", + "command": "-f" + }, + { + "key": "cmd+d", + "command": "d" + }, + { + "key": "cmd+c", + "command": "-c" + }, + { + "key": "cmd+d", + "command": "c", + "when": "context1" + }, + { + "key": "alt+a", + "command": "f" + }, + { + "key": "alt+e", + "command": "e" + }, + { + "key": "alt+g", + "command": "g", + "when": "context2" + } + ] + ======= + [ + { + "key": "alt+a", + "command": "f" + }, + { + "key": "cmd+c", + "command": "-c" + }, + { + "key": "cmd+d", + "command": "d" + }, + { + "key": "alt+d", + "command": "-f" + }, + { + "key": "alt+c", + "command": "c", + "when": "context1" + }, + { + "key": "alt+g", + "command": "g", + "when": "context2" + } + ] + >>>>>>> remote`); + }); - test('merge when local and remote with one entry but different value', async () => { - const localContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const actual = mergeKeybindings(localContent, remoteContent, null); - assert.ok(actual.hasChanges); - assert.ok(actual.hasConflicts); - assert.equal(actual.mergeContent, - `<<<<<<< local -[ - { - "key": "alt+d", - "command": "a", - "when": "editorTextFocus && !editorReadonly" - } -] -======= -[ - { - "key": "alt+c", - "command": "a", - "when": "editorTextFocus && !editorReadonly" - } -] ->>>>>>> remote`); }); +} - test('merge when local and remote with different keybinding', async () => { - const localContent = stringify([ - { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, - { key: 'alt+a', command: '-a', when: 'editorTextFocus && !editorReadonly' } - ]); - const remoteContent = stringify([ - { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, - { key: 'alt+a', command: '-a', when: 'editorTextFocus && !editorReadonly' } - ]); - const actual = mergeKeybindings(localContent, remoteContent, null); - assert.ok(actual.hasChanges); - assert.ok(actual.hasConflicts); - assert.equal(actual.mergeContent, - `<<<<<<< local -[ - { - "key": "alt+d", - "command": "a", - "when": "editorTextFocus && !editorReadonly" - }, - { - "key": "alt+a", - "command": "-a", - "when": "editorTextFocus && !editorReadonly" - } -] -======= -[ - { - "key": "alt+c", - "command": "a", - "when": "editorTextFocus && !editorReadonly" - }, - { - "key": "alt+a", - "command": "-a", - "when": "editorTextFocus && !editorReadonly" - } -] ->>>>>>> remote`); - }); - - test('merge when the entry is removed in local but updated in remote', async () => { - const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const localContent = stringify([]); - const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const actual = mergeKeybindings(localContent, remoteContent, baseContent); - assert.ok(actual.hasChanges); - assert.ok(actual.hasConflicts); - assert.equal(actual.mergeContent, - `<<<<<<< local -[] -======= -[ - { - "key": "alt+c", - "command": "a", - "when": "editorTextFocus && !editorReadonly" - } -] ->>>>>>> remote`); - }); - - test('merge when the entry is removed in local but updated in remote and a new entry is added in local', async () => { - const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const localContent = stringify([{ key: 'alt+b', command: 'b' }]); - const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const actual = mergeKeybindings(localContent, remoteContent, baseContent); - assert.ok(actual.hasChanges); - assert.ok(actual.hasConflicts); - assert.equal(actual.mergeContent, - `<<<<<<< local -[ - { - "key": "alt+b", - "command": "b" - } -] -======= -[ - { - "key": "alt+c", - "command": "a", - "when": "editorTextFocus && !editorReadonly" - } -] ->>>>>>> remote`); - }); - - test('merge when the entry is removed in remote but updated in local', async () => { - const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const remoteContent = stringify([]); - const actual = mergeKeybindings(localContent, remoteContent, baseContent); - assert.ok(actual.hasChanges); - assert.ok(actual.hasConflicts); - assert.equal(actual.mergeContent, - `<<<<<<< local -[ - { - "key": "alt+c", - "command": "a", - "when": "editorTextFocus && !editorReadonly" - } -] -======= -[] ->>>>>>> remote`); - }); - - test('merge when the entry is removed in remote but updated in local and a new entry is added in remote', async () => { - const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const remoteContent = stringify([{ key: 'alt+b', command: 'b' }]); - const actual = mergeKeybindings(localContent, remoteContent, baseContent); - assert.ok(actual.hasChanges); - assert.ok(actual.hasConflicts); - assert.equal(actual.mergeContent, - `<<<<<<< local -[ - { - "key": "alt+c", - "command": "a", - "when": "editorTextFocus && !editorReadonly" - }, - { - "key": "alt+b", - "command": "b" - } -] -======= -[ - { - "key": "alt+b", - "command": "b" - } -] ->>>>>>> remote`); - }); - - test('merge when local and remote has moved forwareded with conflicts', async () => { - const baseContent = stringify([ - { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, - { key: 'alt+c', command: '-a' }, - { key: 'cmd+e', command: 'd' }, - { key: 'alt+a', command: 'f' }, - { key: 'alt+d', command: '-f' }, - { key: 'cmd+d', command: 'c', when: 'context1' }, - { key: 'cmd+c', command: '-c' }, - ]); - const localContent = stringify([ - { key: 'alt+d', command: '-f' }, - { key: 'cmd+e', command: 'd' }, - { key: 'cmd+c', command: '-c' }, - { key: 'cmd+d', command: 'c', when: 'context1' }, - { key: 'alt+a', command: 'f' }, - { key: 'alt+e', command: 'e' }, - ]); - const remoteContent = stringify([ - { key: 'alt+a', command: 'f' }, - { key: 'cmd+c', command: '-c' }, - { key: 'cmd+d', command: 'd' }, - { key: 'alt+d', command: '-f' }, - { key: 'alt+c', command: 'c', when: 'context1' }, - { key: 'alt+g', command: 'g', when: 'context2' }, - ]); - const actual = mergeKeybindings(localContent, remoteContent, baseContent); - assert.ok(actual.hasChanges); - assert.ok(actual.hasConflicts); - assert.equal(actual.mergeContent, - `<<<<<<< local -[ - { - "key": "alt+d", - "command": "-f" - }, - { - "key": "cmd+d", - "command": "d" - }, - { - "key": "cmd+c", - "command": "-c" - }, - { - "key": "cmd+d", - "command": "c", - "when": "context1" - }, - { - "key": "alt+a", - "command": "f" - }, - { - "key": "alt+e", - "command": "e" - }, - { - "key": "alt+g", - "command": "g", - "when": "context2" - } -] -======= -[ - { - "key": "alt+a", - "command": "f" - }, - { - "key": "cmd+c", - "command": "-c" - }, - { - "key": "cmd+d", - "command": "d" - }, - { - "key": "alt+d", - "command": "-f" - }, - { - "key": "alt+c", - "command": "c", - "when": "context1" - }, - { - "key": "alt+g", - "command": "g", - "when": "context2" - } -] ->>>>>>> remote`); - }); - -}); - -function mergeKeybindings(localContent: string, remoteContent: string, baseContent: string | null) { - const local = parse(localContent); - const remote = parse(remoteContent); - const base = baseContent ? parse(baseContent) : null; - const keys: IStringDictionary = {}; - for (const keybinding of [...local, ...remote, ...(base || [])]) { - keys[keybinding.key] = keybinding.key; - } - return merge(localContent, remoteContent, baseContent, keys); +async function mergeKeybindings(localContent: string, remoteContent: string, baseContent: string | null) { + const userDataSyncUtilService = new MockUserDataSyncUtilService(); + const formattingOptions = await userDataSyncUtilService.resolveFormattingOptions(); + return merge(localContent, remoteContent, baseContent, formattingOptions, userDataSyncUtilService); } function stringify(value: any): any { return JSON.stringify(value, null, '\t'); } + +class MockUserDataSyncUtilService implements IUserDataSyncUtilService { + + _serviceBrand: any; + + async resolveUserBindings(userbindings: string[]): Promise> { + const keys: IStringDictionary = {}; + for (const keybinding of userbindings) { + keys[keybinding] = keybinding; + } + return keys; + } + + async resolveFormattingOptions(file?: URI): Promise { + return { eol: OS === OperatingSystem.Linux || OS === OperatingSystem.Macintosh ? '\n' : '\r\n', insertSpaces: false, tabSize: 4 }; + } +} diff --git a/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts b/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts index 3c94e35f321..1ff66d24159 100644 --- a/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts +++ b/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts @@ -4,22 +4,22 @@ *--------------------------------------------------------------------------------------------*/ import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { ISettingsMergeService, IUserKeybindingsResolverService } from 'vs/platform/userDataSync/common/userDataSync'; +import { ISettingsMergeService, IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; import { Registry } from 'vs/platform/registry/common/platform'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { SettingsMergeChannel } from 'vs/platform/userDataSync/common/settingsSyncIpc'; -import { UserKeybindingsResolverServiceChannel } from 'vs/platform/userDataSync/common/keybindingsSyncIpc'; +import { UserDataSycnUtilServiceChannel } from 'vs/platform/userDataSync/common/keybindingsSyncIpc'; class UserDataSyncServicesContribution implements IWorkbenchContribution { constructor( @ISettingsMergeService settingsMergeService: ISettingsMergeService, - @IUserKeybindingsResolverService keybindingsMergeService: IUserKeybindingsResolverService, + @IUserDataSyncUtilService userDataSyncUtilService: IUserDataSyncUtilService, @ISharedProcessService sharedProcessService: ISharedProcessService, ) { sharedProcessService.registerChannel('settingsMerge', new SettingsMergeChannel(settingsMergeService)); - sharedProcessService.registerChannel('userKeybindingsResolver', new UserKeybindingsResolverServiceChannel(keybindingsMergeService)); + sharedProcessService.registerChannel('userDataSyncUtil', new UserDataSycnUtilServiceChannel(userDataSyncUtilService)); } } diff --git a/src/vs/workbench/services/userDataSync/common/keybindingsMerge.ts b/src/vs/workbench/services/userDataSync/common/keybindingsMerge.ts deleted file mode 100644 index d263bfa18d5..00000000000 --- a/src/vs/workbench/services/userDataSync/common/keybindingsMerge.ts +++ /dev/null @@ -1,32 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { parse } from 'vs/base/common/json'; -import { IUserFriendlyKeybinding, IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IUserKeybindingsResolverService } from 'vs/platform/userDataSync/common/userDataSync'; -import { IStringDictionary } from 'vs/base/common/collections'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; - -export class UserKeybindingsResolverService implements IUserKeybindingsResolverService { - - _serviceBrand: undefined; - - constructor( - @IKeybindingService private readonly keybindingsService: IKeybindingService - ) { } - - public async resolveUserKeybindings(localContent: string, remoteContent: string, baseContent: string | null): Promise> { - const local = parse(localContent); - const remote = parse(remoteContent); - const base = baseContent ? parse(baseContent) : null; - const keys: IStringDictionary = {}; - for (const keybinding of [...local, ...remote, ...(base || [])]) { - keys[keybinding.key] = this.keybindingsService.resolveUserBinding(keybinding.key).map(part => part.getUserSettingsLabel()).join(' '); - } - return keys; - } -} - -registerSingleton(IUserKeybindingsResolverService, UserKeybindingsResolverService); diff --git a/src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts b/src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts new file mode 100644 index 00000000000..237e55c17e4 --- /dev/null +++ b/src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts @@ -0,0 +1,40 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IStringDictionary } from 'vs/base/common/collections'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { FormattingOptions } from 'vs/base/common/jsonFormatter'; +import { URI } from 'vs/base/common/uri'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; + +class UserDataSyncUtilService implements IUserDataSyncUtilService { + + _serviceBrand: undefined; + + constructor( + @IKeybindingService private readonly keybindingsService: IKeybindingService, + @ITextModelService private readonly textModelService: ITextModelService, + ) { } + + public async resolveUserBindings(userBindings: string[]): Promise> { + const keys: IStringDictionary = {}; + for (const userbinding of userBindings) { + keys[userbinding] = this.keybindingsService.resolveUserBinding(userbinding).map(part => part.getUserSettingsLabel()).join(' '); + } + return keys; + } + + async resolveFormattingOptions(resource: URI): Promise { + const modelReference = await this.textModelService.createModelReference(resource); + const { insertSpaces, tabSize } = modelReference.object.textEditorModel.getOptions(); + const eol = modelReference.object.textEditorModel.getEOL(); + modelReference.dispose(); + return { eol, insertSpaces, tabSize }; + } +} + +registerSingleton(IUserDataSyncUtilService, UserDataSyncUtilService); diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 316b630b15a..d7de6c214e4 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -80,7 +80,7 @@ import 'vs/workbench/services/extensionManagement/common/extensionEnablementServ import 'vs/workbench/services/notification/common/notificationService'; import 'vs/workbench/services/extensions/common/staticExtensions'; import 'vs/workbench/services/userDataSync/common/settingsMergeService'; -import 'vs/workbench/services/userDataSync/common/keybindingsMerge'; +import 'vs/workbench/services/userDataSync/common/userDataSyncUtil'; import 'vs/workbench/services/path/common/remotePathService'; import 'vs/workbench/services/remote/common/remoteExplorerService'; import 'vs/workbench/services/workingCopy/common/workingCopyService'; From c26146d7c5a7b9b4b01e7b5d758fae087dea834a Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 2 Dec 2019 11:26:29 +0100 Subject: [PATCH 061/255] fix https://github.com/microsoft/vscode-remote-release/issues/1891 --- src/vs/base/browser/markdownRenderer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/base/browser/markdownRenderer.ts b/src/vs/base/browser/markdownRenderer.ts index 11e53eb44ba..0a80b7cf934 100644 --- a/src/vs/base/browser/markdownRenderer.ts +++ b/src/vs/base/browser/markdownRenderer.ts @@ -63,7 +63,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende if (uri.query) { uri = uri.with({ query: _uriMassage(uri.query) }); } - return uri.toString(); + return uri.toString(true); }; // signal to code-block render that the @@ -178,7 +178,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende renderer }; - const allowedSchemes = [Schemas.http, Schemas.https, Schemas.mailto, Schemas.data, Schemas.file, Schemas.vscodeRemote]; + const allowedSchemes = [Schemas.http, Schemas.https, Schemas.mailto, Schemas.data, Schemas.file, Schemas.vscodeRemote, Schemas.vscodeRemoteResource]; if (markdown.isTrusted) { allowedSchemes.push(Schemas.command); } From ef582f49ad69b5dfddc9e236a3eb501703a78c9f Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Mon, 2 Dec 2019 11:47:09 +0100 Subject: [PATCH 062/255] Add a few more token scopes to themes Fixes https://github.com/microsoft/vscode/issues/80783 --- .../cpp/test/colorize-fixtures/test.cpp | 6 + .../test/colorize-results/test-23630_cpp.json | 16 +- .../test/colorize-results/test-23850_cpp.json | 16 +- .../test/colorize-results/test-78769_cpp.json | 8 +- .../cpp/test/colorize-results/test_cc.json | 16 +- .../cpp/test/colorize-results/test_cpp.json | 569 +++++++++++++++++- .../theme-defaults/themes/dark_plus.json | 3 +- extensions/theme-defaults/themes/dark_vs.json | 4 +- .../theme-defaults/themes/light_plus.json | 3 +- .../theme-defaults/themes/light_vs.json | 4 +- .../services/themes/common/colorThemeData.ts | 2 +- 11 files changed, 610 insertions(+), 37 deletions(-) diff --git a/extensions/cpp/test/colorize-fixtures/test.cpp b/extensions/cpp/test/colorize-fixtures/test.cpp index c3c3c10a953..13ec47a554c 100644 --- a/extensions/cpp/test/colorize-fixtures/test.cpp +++ b/extensions/cpp/test/colorize-fixtures/test.cpp @@ -16,7 +16,13 @@ void Rectangle::set_values (int x, int y) { height = y; } +long double operator "" _w(long double); +#define MY_MACRO(a, b) + int main () { + 1.2_w; // calls operator "" _w(1.2L) + asm("movl %a %b"); + MY_MACRO(1, 2); Rectangle rect; rect.set_values (3,4); cout << "area: " << rect.area(); diff --git a/extensions/cpp/test/colorize-results/test-23630_cpp.json b/extensions/cpp/test/colorize-results/test-23630_cpp.json index 08d81e6afff..4ffc7f01f98 100644 --- a/extensions/cpp/test/colorize-results/test-23630_cpp.json +++ b/extensions/cpp/test/colorize-results/test-23630_cpp.json @@ -36,10 +36,10 @@ "c": "_UCRT", "t": "source.cpp meta.preprocessor.conditional.cpp entity.name.function.preprocessor.cpp", "r": { - "dark_plus": "entity.name.function: #DCDCAA", - "light_plus": "entity.name.function: #795E26", - "dark_vs": "meta.preprocessor: #569CD6", - "light_vs": "meta.preprocessor: #0000FF", + "dark_plus": "entity.name.function.preprocessor: #569CD6", + "light_plus": "entity.name.function.preprocessor: #0000FF", + "dark_vs": "entity.name.function.preprocessor: #569CD6", + "light_vs": "entity.name.function.preprocessor: #0000FF", "hc_black": "entity.name.function: #DCDCAA" } }, @@ -91,10 +91,10 @@ "c": "_UCRT", "t": "source.cpp meta.preprocessor.macro.cpp entity.name.function.preprocessor.cpp", "r": { - "dark_plus": "entity.name.function: #DCDCAA", - "light_plus": "entity.name.function: #795E26", - "dark_vs": "meta.preprocessor: #569CD6", - "light_vs": "meta.preprocessor: #0000FF", + "dark_plus": "entity.name.function.preprocessor: #569CD6", + "light_plus": "entity.name.function.preprocessor: #0000FF", + "dark_vs": "entity.name.function.preprocessor: #569CD6", + "light_vs": "entity.name.function.preprocessor: #0000FF", "hc_black": "entity.name.function: #DCDCAA" } }, diff --git a/extensions/cpp/test/colorize-results/test-23850_cpp.json b/extensions/cpp/test/colorize-results/test-23850_cpp.json index 4ba6b59dc9b..a97f9612665 100644 --- a/extensions/cpp/test/colorize-results/test-23850_cpp.json +++ b/extensions/cpp/test/colorize-results/test-23850_cpp.json @@ -36,10 +36,10 @@ "c": "_UCRT", "t": "source.cpp meta.preprocessor.conditional.cpp entity.name.function.preprocessor.cpp", "r": { - "dark_plus": "entity.name.function: #DCDCAA", - "light_plus": "entity.name.function: #795E26", - "dark_vs": "meta.preprocessor: #569CD6", - "light_vs": "meta.preprocessor: #0000FF", + "dark_plus": "entity.name.function.preprocessor: #569CD6", + "light_plus": "entity.name.function.preprocessor: #0000FF", + "dark_vs": "entity.name.function.preprocessor: #569CD6", + "light_vs": "entity.name.function.preprocessor: #0000FF", "hc_black": "entity.name.function: #DCDCAA" } }, @@ -80,10 +80,10 @@ "c": "_UCRT", "t": "source.cpp meta.preprocessor.macro.cpp entity.name.function.preprocessor.cpp", "r": { - "dark_plus": "entity.name.function: #DCDCAA", - "light_plus": "entity.name.function: #795E26", - "dark_vs": "meta.preprocessor: #569CD6", - "light_vs": "meta.preprocessor: #0000FF", + "dark_plus": "entity.name.function.preprocessor: #569CD6", + "light_plus": "entity.name.function.preprocessor: #0000FF", + "dark_vs": "entity.name.function.preprocessor: #569CD6", + "light_vs": "entity.name.function.preprocessor: #0000FF", "hc_black": "entity.name.function: #DCDCAA" } }, diff --git a/extensions/cpp/test/colorize-results/test-78769_cpp.json b/extensions/cpp/test/colorize-results/test-78769_cpp.json index 1d82d5c4e43..72bb9cdede7 100644 --- a/extensions/cpp/test/colorize-results/test-78769_cpp.json +++ b/extensions/cpp/test/colorize-results/test-78769_cpp.json @@ -36,10 +36,10 @@ "c": "DOCTEST_IMPLEMENT_FIXTURE", "t": "source.cpp meta.preprocessor.macro.cpp entity.name.function.preprocessor.cpp", "r": { - "dark_plus": "entity.name.function: #DCDCAA", - "light_plus": "entity.name.function: #795E26", - "dark_vs": "meta.preprocessor: #569CD6", - "light_vs": "meta.preprocessor: #0000FF", + "dark_plus": "entity.name.function.preprocessor: #569CD6", + "light_plus": "entity.name.function.preprocessor: #0000FF", + "dark_vs": "entity.name.function.preprocessor: #569CD6", + "light_vs": "entity.name.function.preprocessor: #0000FF", "hc_black": "entity.name.function: #DCDCAA" } }, diff --git a/extensions/cpp/test/colorize-results/test_cc.json b/extensions/cpp/test/colorize-results/test_cc.json index 902ef645164..30c22384617 100644 --- a/extensions/cpp/test/colorize-results/test_cc.json +++ b/extensions/cpp/test/colorize-results/test_cc.json @@ -36,10 +36,10 @@ "c": "B4G_DEBUG_CHECK", "t": "source.cpp meta.preprocessor.conditional.cpp entity.name.function.preprocessor.cpp", "r": { - "dark_plus": "entity.name.function: #DCDCAA", - "light_plus": "entity.name.function: #795E26", - "dark_vs": "meta.preprocessor: #569CD6", - "light_vs": "meta.preprocessor: #0000FF", + "dark_plus": "entity.name.function.preprocessor: #569CD6", + "light_plus": "entity.name.function.preprocessor: #0000FF", + "dark_vs": "entity.name.function.preprocessor: #569CD6", + "light_vs": "entity.name.function.preprocessor: #0000FF", "hc_black": "entity.name.function: #DCDCAA" } }, @@ -1807,10 +1807,10 @@ "c": "movw $0x38, %ax; ltr %ax", "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp meta.asm.cpp string.quoted.double.cpp meta.embedded.assembly.cpp", "r": { - "dark_plus": "meta.embedded: #D4D4D4", - "light_plus": "meta.embedded: #000000", - "dark_vs": "meta.embedded: #D4D4D4", - "light_vs": "meta.embedded: #000000", + "dark_plus": "meta.embedded.assembly: #CE9178", + "light_plus": "meta.embedded.assembly: #A31515", + "dark_vs": "meta.embedded.assembly: #CE9178", + "light_vs": "meta.embedded.assembly: #A31515", "hc_black": "meta.embedded: #FFFFFF" } }, diff --git a/extensions/cpp/test/colorize-results/test_cpp.json b/extensions/cpp/test/colorize-results/test_cpp.json index f84d916afa3..4806798686a 100644 --- a/extensions/cpp/test/colorize-results/test_cpp.json +++ b/extensions/cpp/test/colorize-results/test_cpp.json @@ -190,10 +190,10 @@ "c": "EXTERN_C", "t": "source.cpp meta.preprocessor.macro.cpp entity.name.function.preprocessor.cpp", "r": { - "dark_plus": "entity.name.function: #DCDCAA", - "light_plus": "entity.name.function: #795E26", - "dark_vs": "meta.preprocessor: #569CD6", - "light_vs": "meta.preprocessor: #0000FF", + "dark_plus": "entity.name.function.preprocessor: #569CD6", + "light_plus": "entity.name.function.preprocessor: #0000FF", + "dark_vs": "entity.name.function.preprocessor: #569CD6", + "light_vs": "entity.name.function.preprocessor: #0000FF", "hc_black": "entity.name.function: #DCDCAA" } }, @@ -1011,6 +1011,281 @@ "hc_black": "default: #FFFFFF" } }, + { + "c": "long", + "t": "source.cpp meta.function.definition.special.operator-overload.cpp meta.qualified_type.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": " ", + "t": "source.cpp meta.function.definition.special.operator-overload.cpp meta.qualified_type.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "double", + "t": "source.cpp meta.function.definition.special.operator-overload.cpp meta.qualified_type.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": " ", + "t": "source.cpp meta.function.definition.special.operator-overload.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "operator", + "t": "source.cpp meta.function.definition.special.operator-overload.cpp meta.head.function.definition.special.operator-overload.cpp keyword.other.operator.overload.cpp", + "r": { + "dark_plus": "keyword.other.operator: #C586C0", + "light_plus": "keyword.other.operator: #AF00DB", + "dark_vs": "keyword: #569CD6", + "light_vs": "keyword: #0000FF", + "hc_black": "keyword.other.operator: #C586C0" + } + }, + { + "c": " ", + "t": "source.cpp meta.function.definition.special.operator-overload.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "\"\"", + "t": "source.cpp meta.function.definition.special.operator-overload.cpp meta.head.function.definition.special.operator-overload.cpp entity.name.operator.custom-literal.cpp", + "r": { + "dark_plus": "entity.name.operator.custom-literal: #DCDCAA", + "light_plus": "entity.name.operator.custom-literal: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cpp meta.function.definition.special.operator-overload.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "_w", + "t": "source.cpp meta.function.definition.special.operator-overload.cpp meta.head.function.definition.special.operator-overload.cpp entity.name.operator.custom-literal.cpp", + "r": { + "dark_plus": "entity.name.operator.custom-literal: #DCDCAA", + "light_plus": "entity.name.operator.custom-literal: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "(", + "t": "source.cpp meta.function.definition.special.operator-overload.cpp meta.head.function.definition.special.operator-overload.cpp punctuation.section.parameters.begin.bracket.round.special.operator-overload.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "long", + "t": "source.cpp meta.function.definition.special.operator-overload.cpp meta.head.function.definition.special.operator-overload.cpp meta.function.definition.parameters.special.operator-overload.cpp meta.parameter.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": " ", + "t": "source.cpp meta.function.definition.special.operator-overload.cpp meta.head.function.definition.special.operator-overload.cpp meta.function.definition.parameters.special.operator-overload.cpp meta.parameter.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "double", + "t": "source.cpp meta.function.definition.special.operator-overload.cpp meta.head.function.definition.special.operator-overload.cpp meta.function.definition.parameters.special.operator-overload.cpp meta.parameter.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": ")", + "t": "source.cpp meta.function.definition.special.operator-overload.cpp meta.head.function.definition.special.operator-overload.cpp punctuation.section.parameters.end.bracket.round.special.operator-overload.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cpp punctuation.terminator.statement.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "#", + "t": "source.cpp meta.preprocessor.macro.cpp keyword.control.directive.define.cpp punctuation.definition.directive.cpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": "define", + "t": "source.cpp meta.preprocessor.macro.cpp keyword.control.directive.define.cpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": " ", + "t": "source.cpp meta.preprocessor.macro.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "MY_MACRO", + "t": "source.cpp meta.preprocessor.macro.cpp entity.name.function.preprocessor.cpp", + "r": { + "dark_plus": "entity.name.function.preprocessor: #569CD6", + "light_plus": "entity.name.function.preprocessor: #0000FF", + "dark_vs": "entity.name.function.preprocessor: #569CD6", + "light_vs": "entity.name.function.preprocessor: #0000FF", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.cpp meta.preprocessor.macro.cpp punctuation.definition.parameters.begin.preprocessor.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "a", + "t": "source.cpp meta.preprocessor.macro.cpp variable.parameter.preprocessor.cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": ",", + "t": "source.cpp meta.preprocessor.macro.cpp punctuation.separator.parameters.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": " ", + "t": "source.cpp meta.preprocessor.macro.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "b", + "t": "source.cpp meta.preprocessor.macro.cpp variable.parameter.preprocessor.cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": ")", + "t": "source.cpp meta.preprocessor.macro.cpp punctuation.definition.parameters.end.preprocessor.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, { "c": "int", "t": "source.cpp meta.function.definition.cpp meta.qualified_type.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", @@ -1099,6 +1374,292 @@ "hc_black": "default: #FFFFFF" } }, + { + "c": " ", + "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "1", + "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp constant.numeric.decimal.cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #09885A", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #09885A", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": ".", + "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp constant.numeric.decimal.point.cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #09885A", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #09885A", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": "2", + "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp constant.numeric.decimal.cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #09885A", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #09885A", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": "_w", + "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp keyword.other.unit.user-defined.cpp", + "r": { + "dark_plus": "keyword.other.unit: #B5CEA8", + "light_plus": "keyword.other.unit: #09885A", + "dark_vs": "keyword.other.unit: #B5CEA8", + "light_vs": "keyword.other.unit: #09885A", + "hc_black": "keyword.other.unit: #B5CEA8" + } + }, + { + "c": ";", + "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp punctuation.terminator.statement.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp comment.line.double-slash.cpp", + "r": { + "dark_plus": "comment: #6A9955", + "light_plus": "comment: #008000", + "dark_vs": "comment: #6A9955", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668" + } + }, + { + "c": "//", + "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp comment.line.double-slash.cpp punctuation.definition.comment.cpp", + "r": { + "dark_plus": "comment: #6A9955", + "light_plus": "comment: #008000", + "dark_vs": "comment: #6A9955", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668" + } + }, + { + "c": " calls operator \"\" _w(1.2L)", + "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp comment.line.double-slash.cpp", + "r": { + "dark_plus": "comment: #6A9955", + "light_plus": "comment: #008000", + "dark_vs": "comment: #6A9955", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668" + } + }, + { + "c": " ", + "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "asm", + "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp meta.asm.cpp storage.type.asm.cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": "(", + "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp meta.asm.cpp punctuation.section.parens.begin.bracket.round.assembly.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "\"", + "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp meta.asm.cpp string.quoted.double.cpp punctuation.definition.string.begin.assembly.cpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "movl %a %b", + "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp meta.asm.cpp string.quoted.double.cpp meta.embedded.assembly.cpp", + "r": { + "dark_plus": "meta.embedded.assembly: #CE9178", + "light_plus": "meta.embedded.assembly: #A31515", + "dark_vs": "meta.embedded.assembly: #CE9178", + "light_vs": "meta.embedded.assembly: #A31515", + "hc_black": "meta.embedded: #FFFFFF" + } + }, + { + "c": "\"", + "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp meta.asm.cpp string.quoted.double.cpp punctuation.definition.string.end.assembly.cpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": ")", + "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp meta.asm.cpp punctuation.section.parens.end.bracket.round.assembly.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp punctuation.terminator.statement.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "MY_MACRO", + "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp entity.name.function.call.cpp", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp punctuation.section.arguments.begin.bracket.round.function.call.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "1", + "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp constant.numeric.decimal.cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #09885A", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #09885A", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": ",", + "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp punctuation.separator.delimiter.comma.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "2", + "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp constant.numeric.decimal.cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #09885A", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #09885A", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": ")", + "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp punctuation.section.arguments.end.bracket.round.function.call.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp punctuation.terminator.statement.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, { "c": " Rectangle rect", "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp", diff --git a/extensions/theme-defaults/themes/dark_plus.json b/extensions/theme-defaults/themes/dark_plus.json index f8a0ff5b7c5..03e62612d62 100644 --- a/extensions/theme-defaults/themes/dark_plus.json +++ b/extensions/theme-defaults/themes/dark_plus.json @@ -9,7 +9,8 @@ "entity.name.function", "support.function", "support.constant.handlebars", - "source.powershell variable.other.member" + "source.powershell variable.other.member", + "entity.name.operator.custom-literal" // See https://en.cppreference.com/w/cpp/language/user_literal ], "settings": { "foreground": "#DCDCAA" diff --git a/extensions/theme-defaults/themes/dark_vs.json b/extensions/theme-defaults/themes/dark_vs.json index 479b5fd8988..6e5f4c25f80 100644 --- a/extensions/theme-defaults/themes/dark_vs.json +++ b/extensions/theme-defaults/themes/dark_vs.json @@ -171,7 +171,8 @@ }, { "scope": [ - "meta.preprocessor" + "meta.preprocessor", + "entity.name.function.preprocessor" ], "settings": { "foreground": "#569cd6" @@ -226,6 +227,7 @@ "scope": [ "string", "entity.name.operator.custom-literal.string", + "meta.embedded.assembly" ], "settings": { "foreground": "#ce9178" diff --git a/extensions/theme-defaults/themes/light_plus.json b/extensions/theme-defaults/themes/light_plus.json index c7599d60d57..faa2b836c2d 100644 --- a/extensions/theme-defaults/themes/light_plus.json +++ b/extensions/theme-defaults/themes/light_plus.json @@ -9,7 +9,8 @@ "entity.name.function", "support.function", "support.constant.handlebars", - "source.powershell variable.other.member" + "source.powershell variable.other.member", + "entity.name.operator.custom-literal" // See https://en.cppreference.com/w/cpp/language/user_literal ], "settings": { "foreground": "#795E26" diff --git a/extensions/theme-defaults/themes/light_vs.json b/extensions/theme-defaults/themes/light_vs.json index 886d1175c8b..5453787c4ed 100644 --- a/extensions/theme-defaults/themes/light_vs.json +++ b/extensions/theme-defaults/themes/light_vs.json @@ -169,7 +169,8 @@ }, { "scope": [ - "meta.preprocessor" + "meta.preprocessor", + "entity.name.function.preprocessor" ], "settings": { "foreground": "#0000ff" @@ -218,6 +219,7 @@ "scope": [ "string", "entity.name.operator.custom-literal.string", + "meta.embedded.assembly" ], "settings": { "foreground": "#a31515" diff --git a/src/vs/workbench/services/themes/common/colorThemeData.ts b/src/vs/workbench/services/themes/common/colorThemeData.ts index 9639f59e0d1..d093aca70c1 100644 --- a/src/vs/workbench/services/themes/common/colorThemeData.ts +++ b/src/vs/workbench/services/themes/common/colorThemeData.ts @@ -30,7 +30,7 @@ let tokenClassificationRegistry = getTokenClassificationRegistry(); const tokenGroupToScopesMap = { comments: ['comment', 'punctuation.definition.comment'], - strings: ['string'], + strings: ['string', 'meta.embedded.assembly'], keywords: ['keyword - keyword.operator', 'keyword.control', 'storage', 'storage.type'], numbers: ['constant.numeric'], types: ['entity.name.type', 'entity.name.class', 'support.type', 'support.class'], From 2387fd48723a0bb7d7ee66697c69d0507fafd62e Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 2 Dec 2019 11:52:37 +0100 Subject: [PATCH 063/255] fix tests --- .../test/common/keybindingsMerge.test.ts | 318 +++++++++--------- 1 file changed, 160 insertions(+), 158 deletions(-) diff --git a/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts b/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts index d6cce88a7bd..dfbbb35e4bf 100644 --- a/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts +++ b/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts @@ -452,24 +452,26 @@ if (OS !== OperatingSystem.Windows) { const actual = await mergeKeybindings(localContent, remoteContent, null); assert.ok(actual.hasChanges); assert.ok(actual.hasConflicts); + //'<<<<<<< local\n[\n\t{\n\t\t"key": "alt+d",\n\t\t"command": "a",\n\t\t"when": "editorTextFocus && !editorReadonly"\n\t}\n]\n=======\n[\n\t{\n\t\t"key": "alt+c",\n\t\t"command": "a",\n\t\t"when": "editorTextFocus && !editorReadonly"\n\t}\n]\n>>>>>>> remote' + //'<<<<<<< local\n\t[\n\t\t{\n\t\t\t"key": "alt+d",\n\t\t\t"command": "a",\n\t\t\t"when": "editorTextFocus && !editorReadonly"\n\t\t}\n\t]\n\t=======\n\t[\n\t\t{\n\t\t\t"key": "alt+c",\n\t\t\t"command": "a",\n\t\t\t"when": "editorTextFocus && !editorReadonly"\n\t\t}\n\t]\n\t>>>>>>> remote' assert.equal(actual.mergeContent, `<<<<<<< local - [ - { - "key": "alt+d", - "command": "a", - "when": "editorTextFocus && !editorReadonly" - } - ] - ======= - [ - { - "key": "alt+c", - "command": "a", - "when": "editorTextFocus && !editorReadonly" - } - ] - >>>>>>> remote`); +[ + { + "key": "alt+d", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + } +] +======= +[ + { + "key": "alt+c", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + } +] +>>>>>>> remote`); }); test('merge when local and remote with different keybinding', async () => { @@ -486,32 +488,32 @@ if (OS !== OperatingSystem.Windows) { assert.ok(actual.hasConflicts); assert.equal(actual.mergeContent, `<<<<<<< local - [ - { - "key": "alt+d", - "command": "a", - "when": "editorTextFocus && !editorReadonly" - }, - { - "key": "alt+a", - "command": "-a", - "when": "editorTextFocus && !editorReadonly" - } - ] - ======= - [ - { - "key": "alt+c", - "command": "a", - "when": "editorTextFocus && !editorReadonly" - }, - { - "key": "alt+a", - "command": "-a", - "when": "editorTextFocus && !editorReadonly" - } - ] - >>>>>>> remote`); +[ + { + "key": "alt+d", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + }, + { + "key": "alt+a", + "command": "-a", + "when": "editorTextFocus && !editorReadonly" + } +] +======= +[ + { + "key": "alt+c", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + }, + { + "key": "alt+a", + "command": "-a", + "when": "editorTextFocus && !editorReadonly" + } +] +>>>>>>> remote`); }); test('merge when the entry is removed in local but updated in remote', async () => { @@ -523,16 +525,16 @@ if (OS !== OperatingSystem.Windows) { assert.ok(actual.hasConflicts); assert.equal(actual.mergeContent, `<<<<<<< local - [] - ======= - [ - { - "key": "alt+c", - "command": "a", - "when": "editorTextFocus && !editorReadonly" - } - ] - >>>>>>> remote`); +[] +======= +[ + { + "key": "alt+c", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + } +] +>>>>>>> remote`); }); test('merge when the entry is removed in local but updated in remote and a new entry is added in local', async () => { @@ -544,21 +546,21 @@ if (OS !== OperatingSystem.Windows) { assert.ok(actual.hasConflicts); assert.equal(actual.mergeContent, `<<<<<<< local - [ - { - "key": "alt+b", - "command": "b" - } - ] - ======= - [ - { - "key": "alt+c", - "command": "a", - "when": "editorTextFocus && !editorReadonly" - } - ] - >>>>>>> remote`); +[ + { + "key": "alt+b", + "command": "b" + } +] +======= +[ + { + "key": "alt+c", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + } +] +>>>>>>> remote`); }); test('merge when the entry is removed in remote but updated in local', async () => { @@ -570,16 +572,16 @@ if (OS !== OperatingSystem.Windows) { assert.ok(actual.hasConflicts); assert.equal(actual.mergeContent, `<<<<<<< local - [ - { - "key": "alt+c", - "command": "a", - "when": "editorTextFocus && !editorReadonly" - } - ] - ======= - [] - >>>>>>> remote`); +[ + { + "key": "alt+c", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + } +] +======= +[] +>>>>>>> remote`); }); test('merge when the entry is removed in remote but updated in local and a new entry is added in remote', async () => { @@ -591,25 +593,25 @@ if (OS !== OperatingSystem.Windows) { assert.ok(actual.hasConflicts); assert.equal(actual.mergeContent, `<<<<<<< local - [ - { - "key": "alt+c", - "command": "a", - "when": "editorTextFocus && !editorReadonly" - }, - { - "key": "alt+b", - "command": "b" - } - ] - ======= - [ - { - "key": "alt+b", - "command": "b" - } - ] - >>>>>>> remote`); +[ + { + "key": "alt+c", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + }, + { + "key": "alt+b", + "command": "b" + } +] +======= +[ + { + "key": "alt+b", + "command": "b" + } +] +>>>>>>> remote`); }); test('merge when local and remote has moved forwareded with conflicts', async () => { @@ -643,68 +645,68 @@ if (OS !== OperatingSystem.Windows) { assert.ok(actual.hasConflicts); assert.equal(actual.mergeContent, `<<<<<<< local - [ - { - "key": "alt+d", - "command": "-f" - }, - { - "key": "cmd+d", - "command": "d" - }, - { - "key": "cmd+c", - "command": "-c" - }, - { - "key": "cmd+d", - "command": "c", - "when": "context1" - }, - { - "key": "alt+a", - "command": "f" - }, - { - "key": "alt+e", - "command": "e" - }, - { - "key": "alt+g", - "command": "g", - "when": "context2" - } - ] - ======= - [ - { - "key": "alt+a", - "command": "f" - }, - { - "key": "cmd+c", - "command": "-c" - }, - { - "key": "cmd+d", - "command": "d" - }, - { - "key": "alt+d", - "command": "-f" - }, - { - "key": "alt+c", - "command": "c", - "when": "context1" - }, - { - "key": "alt+g", - "command": "g", - "when": "context2" - } - ] - >>>>>>> remote`); +[ + { + "key": "alt+d", + "command": "-f" + }, + { + "key": "cmd+d", + "command": "d" + }, + { + "key": "cmd+c", + "command": "-c" + }, + { + "key": "cmd+d", + "command": "c", + "when": "context1" + }, + { + "key": "alt+a", + "command": "f" + }, + { + "key": "alt+e", + "command": "e" + }, + { + "key": "alt+g", + "command": "g", + "when": "context2" + } +] +======= +[ + { + "key": "alt+a", + "command": "f" + }, + { + "key": "cmd+c", + "command": "-c" + }, + { + "key": "cmd+d", + "command": "d" + }, + { + "key": "alt+d", + "command": "-f" + }, + { + "key": "alt+c", + "command": "c", + "when": "context1" + }, + { + "key": "alt+g", + "command": "g", + "when": "context2" + } +] +>>>>>>> remote`); }); }); From a361f69d21e43be5a8e1fd8e5bc2c07829df6267 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 2 Dec 2019 12:03:10 +0100 Subject: [PATCH 064/255] enable tests on windows --- .../test/common/keybindingsMerge.test.ts | 210 +++++++++--------- 1 file changed, 102 insertions(+), 108 deletions(-) diff --git a/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts b/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts index dfbbb35e4bf..39e7d151872 100644 --- a/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts +++ b/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts @@ -432,8 +432,6 @@ suite('KeybindingsMerge - No Conflicts', () => { { key: 'alt+e', command: 'e' }, { key: 'alt+g', command: 'g', when: 'context2' }, ]); - //'[\n\t{\n\t\t"key": "alt+d",\n\t\t"command": "-f"\n\t},\n\t{\n\t\t"key": "cmd+d",\n\t\t"command": "d"\n\t},\n\t{\n\t\t"key": "cmd+c",\n\t\t"command": "-c"\n\t},\n\t{\n\t\t"key": "cmd+d",\n\t\t"command": "c",\n\t\t"when": "context1"\n\t},\n\t{\n\t\t"key": "alt+a",\n\t\t"command": "f"\n\t},\n\t{\n\t\t"key": "alt+e",\n\t\t"command": "e"\n\t},\n\t{\n\t\t"key": "alt+g",\n\t\t"command": "g",\n\t\t"when": "context2"\n\t}\n]' - //'[\n\t{\n\t\t"key": "alt+d",\n\t\t"command": "-f"\n\t},\n\t{\n\t\t"key": "cmd+d",\n\t\t"command": "d"\n\t},\n\t{\n\t\t"key": "cmd+c",\n\t\t"command": "-c"\n\t},\n\t{\n\t\t"key": "alt+c",\n\t\t"command": "c",\n\t\t"when": "context1"\n\t},\n\t{\n\t\t"key": "alt+a",\n\t\t"command": "f"\n\t},\n\t{\n\t\t"key": "alt+e",\n\t\t"command": "e"\n\t},\n\t{\n\t\t"key": "alt+g",\n\t\t"command": "g",\n\t\t"when": "context2"\n\t}\n]' const actual = await mergeKeybindings(localContent, remoteContent, baseContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); @@ -442,20 +440,16 @@ suite('KeybindingsMerge - No Conflicts', () => { }); -if (OS !== OperatingSystem.Windows) { +suite('KeybindingsMerge - Conflicts', () => { - suite('KeybindingsMerge - Conflicts', () => { - - test('merge when local and remote with one entry but different value', async () => { - const localContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const actual = await mergeKeybindings(localContent, remoteContent, null); - assert.ok(actual.hasChanges); - assert.ok(actual.hasConflicts); - //'<<<<<<< local\n[\n\t{\n\t\t"key": "alt+d",\n\t\t"command": "a",\n\t\t"when": "editorTextFocus && !editorReadonly"\n\t}\n]\n=======\n[\n\t{\n\t\t"key": "alt+c",\n\t\t"command": "a",\n\t\t"when": "editorTextFocus && !editorReadonly"\n\t}\n]\n>>>>>>> remote' - //'<<<<<<< local\n\t[\n\t\t{\n\t\t\t"key": "alt+d",\n\t\t\t"command": "a",\n\t\t\t"when": "editorTextFocus && !editorReadonly"\n\t\t}\n\t]\n\t=======\n\t[\n\t\t{\n\t\t\t"key": "alt+c",\n\t\t\t"command": "a",\n\t\t\t"when": "editorTextFocus && !editorReadonly"\n\t\t}\n\t]\n\t>>>>>>> remote' - assert.equal(actual.mergeContent, - `<<<<<<< local + test('merge when local and remote with one entry but different value', async () => { + const localContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const actual = await mergeKeybindings(localContent, remoteContent, null); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local [ { "key": "alt+d", @@ -472,22 +466,22 @@ if (OS !== OperatingSystem.Windows) { } ] >>>>>>> remote`); - }); + }); - test('merge when local and remote with different keybinding', async () => { - const localContent = stringify([ - { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, - { key: 'alt+a', command: '-a', when: 'editorTextFocus && !editorReadonly' } - ]); - const remoteContent = stringify([ - { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, - { key: 'alt+a', command: '-a', when: 'editorTextFocus && !editorReadonly' } - ]); - const actual = await mergeKeybindings(localContent, remoteContent, null); - assert.ok(actual.hasChanges); - assert.ok(actual.hasConflicts); - assert.equal(actual.mergeContent, - `<<<<<<< local + test('merge when local and remote with different keybinding', async () => { + const localContent = stringify([ + { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+a', command: '-a', when: 'editorTextFocus && !editorReadonly' } + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+a', command: '-a', when: 'editorTextFocus && !editorReadonly' } + ]); + const actual = await mergeKeybindings(localContent, remoteContent, null); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local [ { "key": "alt+d", @@ -514,17 +508,17 @@ if (OS !== OperatingSystem.Windows) { } ] >>>>>>> remote`); - }); + }); - test('merge when the entry is removed in local but updated in remote', async () => { - const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const localContent = stringify([]); - const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const actual = await mergeKeybindings(localContent, remoteContent, baseContent); - assert.ok(actual.hasChanges); - assert.ok(actual.hasConflicts); - assert.equal(actual.mergeContent, - `<<<<<<< local + test('merge when the entry is removed in local but updated in remote', async () => { + const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const localContent = stringify([]); + const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const actual = await mergeKeybindings(localContent, remoteContent, baseContent); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local [] ======= [ @@ -535,17 +529,17 @@ if (OS !== OperatingSystem.Windows) { } ] >>>>>>> remote`); - }); + }); - test('merge when the entry is removed in local but updated in remote and a new entry is added in local', async () => { - const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const localContent = stringify([{ key: 'alt+b', command: 'b' }]); - const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const actual = await mergeKeybindings(localContent, remoteContent, baseContent); - assert.ok(actual.hasChanges); - assert.ok(actual.hasConflicts); - assert.equal(actual.mergeContent, - `<<<<<<< local + test('merge when the entry is removed in local but updated in remote and a new entry is added in local', async () => { + const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const localContent = stringify([{ key: 'alt+b', command: 'b' }]); + const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const actual = await mergeKeybindings(localContent, remoteContent, baseContent); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local [ { "key": "alt+b", @@ -561,17 +555,17 @@ if (OS !== OperatingSystem.Windows) { } ] >>>>>>> remote`); - }); + }); - test('merge when the entry is removed in remote but updated in local', async () => { - const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const remoteContent = stringify([]); - const actual = await mergeKeybindings(localContent, remoteContent, baseContent); - assert.ok(actual.hasChanges); - assert.ok(actual.hasConflicts); - assert.equal(actual.mergeContent, - `<<<<<<< local + test('merge when the entry is removed in remote but updated in local', async () => { + const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const remoteContent = stringify([]); + const actual = await mergeKeybindings(localContent, remoteContent, baseContent); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local [ { "key": "alt+c", @@ -582,17 +576,17 @@ if (OS !== OperatingSystem.Windows) { ======= [] >>>>>>> remote`); - }); + }); - test('merge when the entry is removed in remote but updated in local and a new entry is added in remote', async () => { - const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const remoteContent = stringify([{ key: 'alt+b', command: 'b' }]); - const actual = await mergeKeybindings(localContent, remoteContent, baseContent); - assert.ok(actual.hasChanges); - assert.ok(actual.hasConflicts); - assert.equal(actual.mergeContent, - `<<<<<<< local + test('merge when the entry is removed in remote but updated in local and a new entry is added in remote', async () => { + const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const remoteContent = stringify([{ key: 'alt+b', command: 'b' }]); + const actual = await mergeKeybindings(localContent, remoteContent, baseContent); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local [ { "key": "alt+c", @@ -612,39 +606,39 @@ if (OS !== OperatingSystem.Windows) { } ] >>>>>>> remote`); - }); + }); - test('merge when local and remote has moved forwareded with conflicts', async () => { - const baseContent = stringify([ - { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, - { key: 'alt+c', command: '-a' }, - { key: 'cmd+e', command: 'd' }, - { key: 'alt+a', command: 'f' }, - { key: 'alt+d', command: '-f' }, - { key: 'cmd+d', command: 'c', when: 'context1' }, - { key: 'cmd+c', command: '-c' }, - ]); - const localContent = stringify([ - { key: 'alt+d', command: '-f' }, - { key: 'cmd+e', command: 'd' }, - { key: 'cmd+c', command: '-c' }, - { key: 'cmd+d', command: 'c', when: 'context1' }, - { key: 'alt+a', command: 'f' }, - { key: 'alt+e', command: 'e' }, - ]); - const remoteContent = stringify([ - { key: 'alt+a', command: 'f' }, - { key: 'cmd+c', command: '-c' }, - { key: 'cmd+d', command: 'd' }, - { key: 'alt+d', command: '-f' }, - { key: 'alt+c', command: 'c', when: 'context1' }, - { key: 'alt+g', command: 'g', when: 'context2' }, - ]); - const actual = await mergeKeybindings(localContent, remoteContent, baseContent); - assert.ok(actual.hasChanges); - assert.ok(actual.hasConflicts); - assert.equal(actual.mergeContent, - `<<<<<<< local + test('merge when local and remote has moved forwareded with conflicts', async () => { + const baseContent = stringify([ + { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+c', command: '-a' }, + { key: 'cmd+e', command: 'd' }, + { key: 'alt+a', command: 'f' }, + { key: 'alt+d', command: '-f' }, + { key: 'cmd+d', command: 'c', when: 'context1' }, + { key: 'cmd+c', command: '-c' }, + ]); + const localContent = stringify([ + { key: 'alt+d', command: '-f' }, + { key: 'cmd+e', command: 'd' }, + { key: 'cmd+c', command: '-c' }, + { key: 'cmd+d', command: 'c', when: 'context1' }, + { key: 'alt+a', command: 'f' }, + { key: 'alt+e', command: 'e' }, + ]); + const remoteContent = stringify([ + { key: 'alt+a', command: 'f' }, + { key: 'cmd+c', command: '-c' }, + { key: 'cmd+d', command: 'd' }, + { key: 'alt+d', command: '-f' }, + { key: 'alt+c', command: 'c', when: 'context1' }, + { key: 'alt+g', command: 'g', when: 'context2' }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, baseContent); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local [ { "key": "alt+d", @@ -707,10 +701,9 @@ if (OS !== OperatingSystem.Windows) { } ] >>>>>>> remote`); - }); - }); -} + +}); async function mergeKeybindings(localContent: string, remoteContent: string, baseContent: string | null) { const userDataSyncUtilService = new MockUserDataSyncUtilService(); @@ -718,8 +711,9 @@ async function mergeKeybindings(localContent: string, remoteContent: string, bas return merge(localContent, remoteContent, baseContent, formattingOptions, userDataSyncUtilService); } -function stringify(value: any): any { - return JSON.stringify(value, null, '\t'); +function stringify(value: any): string { + const result = JSON.stringify(value, null, '\t'); + return OS === OperatingSystem.Windows ? result.replace('\n', '\r\n') : result; } class MockUserDataSyncUtilService implements IUserDataSyncUtilService { @@ -735,6 +729,6 @@ class MockUserDataSyncUtilService implements IUserDataSyncUtilService { } async resolveFormattingOptions(file?: URI): Promise { - return { eol: OS === OperatingSystem.Linux || OS === OperatingSystem.Macintosh ? '\n' : '\r\n', insertSpaces: false, tabSize: 4 }; + return { eol: OS === OperatingSystem.Windows ? '\r\n' : '\n', insertSpaces: false, tabSize: 4 }; } } From b8025bcfef6807873e16bbcaba3ec3d0973b65c5 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 2 Dec 2019 12:12:57 +0100 Subject: [PATCH 065/255] some bulk edit polish --- .../bulkEdit/browser/bulkEditService.ts | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts b/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts index 372826a2ae7..4690972af8b 100644 --- a/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts +++ b/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts @@ -226,17 +226,18 @@ class BulkEditModel implements IDisposable { } } -export type Edit = ResourceFileEdit | ResourceTextEdit; +type Edit = ResourceFileEdit | ResourceTextEdit; -export class BulkEdit { +class BulkEdit { - private _edits: Edit[] = []; - private _editor: ICodeEditor | undefined; - private _progress: IProgress; + private readonly _edits: Edit[] = []; + private readonly _editor: ICodeEditor | undefined; + private readonly _progress: IProgress; constructor( editor: ICodeEditor | undefined, progress: IProgress | undefined, + edits: Edit[], @ILogService private readonly _logService: ILogService, @ITextModelService private readonly _textModelService: ITextModelService, @IFileService private readonly _fileService: IFileService, @@ -246,14 +247,7 @@ export class BulkEdit { ) { this._editor = editor; this._progress = progress || emptyProgress; - } - - add(edits: Edit[] | Edit): void { - if (Array.isArray(edits)) { - this._edits.push(...edits); - } else { - this._edits.push(edits); - } + this._edits = edits; } ariaMessage(): string { @@ -419,8 +413,11 @@ export class BulkEditService implements IBulkEditService { // If the code editor is readonly still allow bulk edits to be applied #68549 codeEditor = undefined; } - const bulkEdit = new BulkEdit(codeEditor, options.progress, this._logService, this._textModelService, this._fileService, this._textFileService, this._labelService, this._configurationService); - bulkEdit.add(edits); + const bulkEdit = new BulkEdit( + codeEditor, options.progress, edits, + this._logService, this._textModelService, this._fileService, this._textFileService, this._labelService, this._configurationService + ); + return bulkEdit.perform().then(() => { return { ariaSummary: bulkEdit.ariaMessage() }; From 99e822efbb4d1805604cad44270262ce9c46e30c Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Mon, 2 Dec 2019 12:32:22 +0100 Subject: [PATCH 066/255] Tweak strings in tunnel UI --- src/vs/platform/remote/common/tunnel.ts | 2 +- .../contrib/remote/browser/tunnelView.ts | 41 ++++++++----------- .../remote/common/remoteExplorerService.ts | 13 +++--- .../services/remote/node/tunnelService.ts | 5 +-- 4 files changed, 27 insertions(+), 34 deletions(-) diff --git a/src/vs/platform/remote/common/tunnel.ts b/src/vs/platform/remote/common/tunnel.ts index ebbef722b59..78187325d07 100644 --- a/src/vs/platform/remote/common/tunnel.ts +++ b/src/vs/platform/remote/common/tunnel.ts @@ -12,7 +12,7 @@ export const ITunnelService = createDecorator('tunnelService'); export interface RemoteTunnel { readonly tunnelRemotePort: number; readonly tunnelLocalPort: number; - readonly localAddress?: URI; + readonly localAddress?: string; dispose(): void; } diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index 8d01b06b61b..f62f7647b3d 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -29,7 +29,6 @@ import { IMenuService, MenuId, IMenu, MenuRegistry, MenuItemAction } from 'vs/pl import { createAndFillInContextMenuActions, createAndFillInActionBarActions, ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IRemoteExplorerService, TunnelModel } from 'vs/workbench/services/remote/common/remoteExplorerService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { URI } from 'vs/workbench/workbench.web.api'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; import { attachInputBoxStyler } from 'vs/platform/theme/common/styler'; @@ -37,6 +36,7 @@ import { once } from 'vs/base/common/functional'; import { KeyCode } from 'vs/base/common/keyCodes'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { URI } from 'vs/base/common/uri'; class TunnelTreeVirtualDelegate implements IListVirtualDelegate { getHeight(element: ITunnelItem): number { @@ -103,13 +103,13 @@ export class TunnelViewModel extends Disposable implements ITunnelViewModel { get forwarded(): TunnelItem[] { return Array.from(this.model.forwarded.values()).map(tunnel => { - return new TunnelItem(TunnelType.Forwarded, tunnel.remote, tunnel.closeable, tunnel.name, tunnel.description, tunnel.local); + return new TunnelItem(TunnelType.Forwarded, tunnel.remote, tunnel.localAddress, tunnel.closeable, tunnel.name, tunnel.description); }); } get published(): TunnelItem[] { return Array.from(this.model.published.values()).map(tunnel => { - return new TunnelItem(TunnelType.Published, tunnel.remote, false, tunnel.name, tunnel.description, tunnel.local); + return new TunnelItem(TunnelType.Published, tunnel.remote, tunnel.localAddress, false, tunnel.name, tunnel.description); }); } @@ -119,7 +119,7 @@ export class TunnelViewModel extends Disposable implements ITunnelViewModel { let iterator = values.next(); while (!iterator.done) { if (!this.model.forwarded.has(iterator.value.remote) && !this.model.published.has(iterator.value.remote)) { - candidates.push(new TunnelItem(TunnelType.Candidate, iterator.value.remote, false, undefined, iterator.value.description)); + candidates.push(new TunnelItem(TunnelType.Candidate, iterator.value.remote, iterator.value.localAddress, false, undefined, iterator.value.description)); } iterator = values.next(); } @@ -333,7 +333,7 @@ interface ITunnelGroup { interface ITunnelItem { tunnelType: TunnelType; remote: number; - local?: number; + localAddress?: string; name?: string; closeable?: boolean; readonly description?: string; @@ -344,33 +344,28 @@ class TunnelItem implements ITunnelItem { constructor( public tunnelType: TunnelType, public remote: number, + public localAddress?: string, public closeable?: boolean, public name?: string, private _description?: string, - public local?: number, ) { } get label(): string { - if (this.name && this.local) { + if (this.name) { return nls.localize('remote.tunnelsView.forwardedPortLabel0', "{0}", this.name); - } else if (this.local === this.remote) { - return nls.localize('remote.tunnelsView.forwardedPortLabel1', "{0} to localhost", this.remote); - } else if (this.local) { - return nls.localize('remote.tunnelsView.forwardedPortLabel2', "{0} to localhost:{1}", this.remote, this.local); + } else if (this.localAddress) { + return nls.localize('remote.tunnelsView.forwardedPortLabel2', "{0} to {1}", this.remote, this.localAddress); } else { return nls.localize('remote.tunnelsView.forwardedPortLabel3', "{0} not forwarded", this.remote); } } get description(): string | undefined { - if (this.name) { - return nls.localize('remote.tunnelsView.forwardedPortDescription0', "remote {0} available at {1}", this.remote, this.local); - } else if (this.local === this.remote) { - return nls.localize('remote.tunnelsView.forwardedPortDescription1', "available at {0}", this.local); - } else if (this.local) { - return this._description; - } else { + if (this._description) { return this._description; + } else if (this.name) { + return nls.localize('remote.tunnelsView.forwardedPortDescription0', "{0} to {1}", this.remote, this.localAddress); } + return undefined; } } @@ -562,7 +557,7 @@ export class TunnelPanelDescriptor implements IViewDescriptor { namespace NameTunnelAction { export const ID = 'remote.tunnel.name'; - export const LABEL = nls.localize('remote.tunnel.name', "Name Tunnel"); + export const LABEL = nls.localize('remote.tunnel.name', "Rename"); export function handler(): ICommandHandler { return async (accessor, arg) => { @@ -576,7 +571,7 @@ namespace NameTunnelAction { remoteExplorerService.setEditable(arg.remote, null); }, validationMessage: () => null, - placeholder: nls.localize('remote.tunnelsView.namePlaceholder', "Name port"), + placeholder: nls.localize('remote.tunnelsView.namePlaceholder', "Port name"), startingValue: arg.name }); } @@ -644,9 +639,9 @@ namespace OpenPortInBrowserAction { const model = accessor.get(IRemoteExplorerService).tunnelModel; const openerService = accessor.get(IOpenerService); const tunnel = model.forwarded.has(arg.remote) ? model.forwarded.get(arg.remote) : model.published.get(arg.remote); - let address: URI | undefined; - if (tunnel && tunnel.localUri && (address = model.address(tunnel.remote))) { - return openerService.open(address); + let address: string | undefined; + if (tunnel && tunnel.localAddress && (address = model.address(tunnel.remote))) { + return openerService.open(URI.parse(address)); } return Promise.resolve(); } diff --git a/src/vs/workbench/services/remote/common/remoteExplorerService.ts b/src/vs/workbench/services/remote/common/remoteExplorerService.ts index 4f2e382f040..f43ba81ec1e 100644 --- a/src/vs/workbench/services/remote/common/remoteExplorerService.ts +++ b/src/vs/workbench/services/remote/common/remoteExplorerService.ts @@ -10,7 +10,6 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { URI } from 'vs/base/common/uri'; import { ITunnelService } from 'vs/platform/remote/common/tunnel'; import { Disposable } from 'vs/base/common/lifecycle'; import { IEditableData } from 'vs/workbench/common/views'; @@ -20,7 +19,7 @@ export const REMOTE_EXPLORER_TYPE_KEY: string = 'remote.explorerType'; export interface Tunnel { remote: number; - localUri: URI; + localAddress: string; local?: number; name?: string; description?: string; @@ -47,7 +46,7 @@ export class TunnelModel extends Disposable { if (tunnel.localAddress) { this.forwarded.set(tunnel.tunnelRemotePort, { remote: tunnel.tunnelRemotePort, - localUri: tunnel.localAddress, + localAddress: tunnel.localAddress, local: tunnel.tunnelLocalPort }); } @@ -63,7 +62,7 @@ export class TunnelModel extends Disposable { if (!this.forwarded.has(tunnel.tunnelRemotePort) && tunnel.localAddress) { this.forwarded.set(tunnel.tunnelRemotePort, { remote: tunnel.tunnelRemotePort, - localUri: tunnel.localAddress, + localAddress: tunnel.localAddress, local: tunnel.tunnelLocalPort }); } @@ -86,7 +85,7 @@ export class TunnelModel extends Disposable { local: tunnel.tunnelLocalPort, name: name, closeable: true, - localUri: tunnel.localAddress + localAddress: tunnel.localAddress }; this.forwarded.set(remote, newForward); this._onForwardPort.fire(newForward); @@ -105,8 +104,8 @@ export class TunnelModel extends Disposable { return this.tunnelService.closeTunnel(remote); } - address(remote: number): URI | undefined { - return (this.forwarded.get(remote) || this.published.get(remote))?.localUri; + address(remote: number): string | undefined { + return (this.forwarded.get(remote) || this.published.get(remote))?.localAddress; } } diff --git a/src/vs/workbench/services/remote/node/tunnelService.ts b/src/vs/workbench/services/remote/node/tunnelService.ts index 30c886758f7..d668ac3d3c6 100644 --- a/src/vs/workbench/services/remote/node/tunnelService.ts +++ b/src/vs/workbench/services/remote/node/tunnelService.ts @@ -17,7 +17,6 @@ import { ISignService } from 'vs/platform/sign/common/sign'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; import { findFreePort } from 'vs/base/node/ports'; -import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; export async function createRemoteTunnel(options: IConnectionOptions, tunnelRemotePort: number, tunnelLocalPort?: number): Promise { @@ -29,7 +28,7 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel { public readonly tunnelRemotePort: number; public tunnelLocalPort!: number; - public localAddress?: URI; + public localAddress?: string; private readonly _options: IConnectionOptions; private readonly _server: net.Server; @@ -71,7 +70,7 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel { this.tunnelLocalPort = address.port; await this._barrier.wait(); - this.localAddress = URI.from({ scheme: 'http', authority: 'localhost:' + address.port }); + this.localAddress = 'localhost:' + address.port; return this; } From 7bc00b2c24eed926c42a0865339cba2f1b936e93 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 2 Dec 2019 13:08:09 +0100 Subject: [PATCH 067/255] skip tests in windowa --- .../test/common/keybindingsMerge.test.ts | 198 +++++++++--------- 1 file changed, 101 insertions(+), 97 deletions(-) diff --git a/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts b/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts index 39e7d151872..c1a38818e55 100644 --- a/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts +++ b/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts @@ -440,16 +440,18 @@ suite('KeybindingsMerge - No Conflicts', () => { }); -suite('KeybindingsMerge - Conflicts', () => { +if (OS !== OperatingSystem.Windows) { - test('merge when local and remote with one entry but different value', async () => { - const localContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const actual = await mergeKeybindings(localContent, remoteContent, null); - assert.ok(actual.hasChanges); - assert.ok(actual.hasConflicts); - assert.equal(actual.mergeContent, - `<<<<<<< local + suite('KeybindingsMerge - Conflicts', () => { + + test('merge when local and remote with one entry but different value', async () => { + const localContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const actual = await mergeKeybindings(localContent, remoteContent, null); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local [ { "key": "alt+d", @@ -466,22 +468,22 @@ suite('KeybindingsMerge - Conflicts', () => { } ] >>>>>>> remote`); - }); + }); - test('merge when local and remote with different keybinding', async () => { - const localContent = stringify([ - { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, - { key: 'alt+a', command: '-a', when: 'editorTextFocus && !editorReadonly' } - ]); - const remoteContent = stringify([ - { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, - { key: 'alt+a', command: '-a', when: 'editorTextFocus && !editorReadonly' } - ]); - const actual = await mergeKeybindings(localContent, remoteContent, null); - assert.ok(actual.hasChanges); - assert.ok(actual.hasConflicts); - assert.equal(actual.mergeContent, - `<<<<<<< local + test('merge when local and remote with different keybinding', async () => { + const localContent = stringify([ + { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+a', command: '-a', when: 'editorTextFocus && !editorReadonly' } + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+a', command: '-a', when: 'editorTextFocus && !editorReadonly' } + ]); + const actual = await mergeKeybindings(localContent, remoteContent, null); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local [ { "key": "alt+d", @@ -508,17 +510,17 @@ suite('KeybindingsMerge - Conflicts', () => { } ] >>>>>>> remote`); - }); + }); - test('merge when the entry is removed in local but updated in remote', async () => { - const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const localContent = stringify([]); - const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const actual = await mergeKeybindings(localContent, remoteContent, baseContent); - assert.ok(actual.hasChanges); - assert.ok(actual.hasConflicts); - assert.equal(actual.mergeContent, - `<<<<<<< local + test('merge when the entry is removed in local but updated in remote', async () => { + const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const localContent = stringify([]); + const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const actual = await mergeKeybindings(localContent, remoteContent, baseContent); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local [] ======= [ @@ -529,17 +531,17 @@ suite('KeybindingsMerge - Conflicts', () => { } ] >>>>>>> remote`); - }); + }); - test('merge when the entry is removed in local but updated in remote and a new entry is added in local', async () => { - const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const localContent = stringify([{ key: 'alt+b', command: 'b' }]); - const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const actual = await mergeKeybindings(localContent, remoteContent, baseContent); - assert.ok(actual.hasChanges); - assert.ok(actual.hasConflicts); - assert.equal(actual.mergeContent, - `<<<<<<< local + test('merge when the entry is removed in local but updated in remote and a new entry is added in local', async () => { + const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const localContent = stringify([{ key: 'alt+b', command: 'b' }]); + const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const actual = await mergeKeybindings(localContent, remoteContent, baseContent); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local [ { "key": "alt+b", @@ -555,17 +557,17 @@ suite('KeybindingsMerge - Conflicts', () => { } ] >>>>>>> remote`); - }); + }); - test('merge when the entry is removed in remote but updated in local', async () => { - const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const remoteContent = stringify([]); - const actual = await mergeKeybindings(localContent, remoteContent, baseContent); - assert.ok(actual.hasChanges); - assert.ok(actual.hasConflicts); - assert.equal(actual.mergeContent, - `<<<<<<< local + test('merge when the entry is removed in remote but updated in local', async () => { + const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const remoteContent = stringify([]); + const actual = await mergeKeybindings(localContent, remoteContent, baseContent); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local [ { "key": "alt+c", @@ -576,17 +578,17 @@ suite('KeybindingsMerge - Conflicts', () => { ======= [] >>>>>>> remote`); - }); + }); - test('merge when the entry is removed in remote but updated in local and a new entry is added in remote', async () => { - const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const remoteContent = stringify([{ key: 'alt+b', command: 'b' }]); - const actual = await mergeKeybindings(localContent, remoteContent, baseContent); - assert.ok(actual.hasChanges); - assert.ok(actual.hasConflicts); - assert.equal(actual.mergeContent, - `<<<<<<< local + test('merge when the entry is removed in remote but updated in local and a new entry is added in remote', async () => { + const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const remoteContent = stringify([{ key: 'alt+b', command: 'b' }]); + const actual = await mergeKeybindings(localContent, remoteContent, baseContent); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local [ { "key": "alt+c", @@ -606,39 +608,39 @@ suite('KeybindingsMerge - Conflicts', () => { } ] >>>>>>> remote`); - }); + }); - test('merge when local and remote has moved forwareded with conflicts', async () => { - const baseContent = stringify([ - { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, - { key: 'alt+c', command: '-a' }, - { key: 'cmd+e', command: 'd' }, - { key: 'alt+a', command: 'f' }, - { key: 'alt+d', command: '-f' }, - { key: 'cmd+d', command: 'c', when: 'context1' }, - { key: 'cmd+c', command: '-c' }, - ]); - const localContent = stringify([ - { key: 'alt+d', command: '-f' }, - { key: 'cmd+e', command: 'd' }, - { key: 'cmd+c', command: '-c' }, - { key: 'cmd+d', command: 'c', when: 'context1' }, - { key: 'alt+a', command: 'f' }, - { key: 'alt+e', command: 'e' }, - ]); - const remoteContent = stringify([ - { key: 'alt+a', command: 'f' }, - { key: 'cmd+c', command: '-c' }, - { key: 'cmd+d', command: 'd' }, - { key: 'alt+d', command: '-f' }, - { key: 'alt+c', command: 'c', when: 'context1' }, - { key: 'alt+g', command: 'g', when: 'context2' }, - ]); - const actual = await mergeKeybindings(localContent, remoteContent, baseContent); - assert.ok(actual.hasChanges); - assert.ok(actual.hasConflicts); - assert.equal(actual.mergeContent, - `<<<<<<< local + test('merge when local and remote has moved forwareded with conflicts', async () => { + const baseContent = stringify([ + { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+c', command: '-a' }, + { key: 'cmd+e', command: 'd' }, + { key: 'alt+a', command: 'f' }, + { key: 'alt+d', command: '-f' }, + { key: 'cmd+d', command: 'c', when: 'context1' }, + { key: 'cmd+c', command: '-c' }, + ]); + const localContent = stringify([ + { key: 'alt+d', command: '-f' }, + { key: 'cmd+e', command: 'd' }, + { key: 'cmd+c', command: '-c' }, + { key: 'cmd+d', command: 'c', when: 'context1' }, + { key: 'alt+a', command: 'f' }, + { key: 'alt+e', command: 'e' }, + ]); + const remoteContent = stringify([ + { key: 'alt+a', command: 'f' }, + { key: 'cmd+c', command: '-c' }, + { key: 'cmd+d', command: 'd' }, + { key: 'alt+d', command: '-f' }, + { key: 'alt+c', command: 'c', when: 'context1' }, + { key: 'alt+g', command: 'g', when: 'context2' }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, baseContent); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local [ { "key": "alt+d", @@ -701,9 +703,11 @@ suite('KeybindingsMerge - Conflicts', () => { } ] >>>>>>> remote`); + }); + }); -}); +} async function mergeKeybindings(localContent: string, remoteContent: string, baseContent: string | null) { const userDataSyncUtilService = new MockUserDataSyncUtilService(); From 4d7b05374841dc96f63c3765f03c92cbf0f97d35 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Mon, 2 Dec 2019 14:18:57 +0100 Subject: [PATCH 068/255] Adopt input box for new port --- .../contrib/remote/browser/tunnelView.ts | 52 +++++++++---------- .../remote/common/remoteExplorerService.ts | 16 +++--- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index f62f7647b3d..8e194bd815f 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -193,17 +193,21 @@ class TunnelTreeRenderer extends Disposable implements ITreeRenderer { - const quickInputService = accessor.get(IQuickInputService); const remoteExplorerService = accessor.get(IRemoteExplorerService); - let remote: number | undefined = undefined; if (arg instanceof TunnelItem) { - remote = arg.remote; + remoteExplorerService.tunnelModel.forward(arg.remote); } else { - const input = parseInt(await quickInputService.input({ placeHolder: nls.localize('remote.tunnelView.pickRemote', 'Remote port to forward') })); - if (typeof input === 'number') { - remote = input; - } + remoteExplorerService.setEditable(undefined, { + onFinish: (value, success) => { + if (success) { + remoteExplorerService.tunnelModel.forward(Number(value)); + } + remoteExplorerService.setEditable(undefined, null); + }, + validationMessage: (value) => { + const asNumber = Number(value); + if (isNaN(asNumber) || (asNumber < 0) || (asNumber > 65535)) { + return nls.localize('remote.tunnelsView.portNumberValid', "Port number is invalid"); + } + return null; + }, + placeholder: nls.localize('remote.tunnelsView.forwardPortPlaceholder', "Port number") + }); } - - if (!remote) { - return; - } - - const local: string = await quickInputService.input({ placeHolder: nls.localize('remote.tunnelView.pickLocal', 'Local port to forward to, or leave blank for {0}', remote) }); - if (local === undefined) { - return; - } - const name = await quickInputService.input({ placeHolder: nls.localize('remote.tunnelView.pickName', 'Port name, or leave blank for no name') }); - if (name === undefined) { - return; - } - await remoteExplorerService.tunnelModel.forward(remote, (local !== '') ? parseInt(local) : remote, (name !== '') ? name : undefined); }; } } @@ -641,7 +641,7 @@ namespace OpenPortInBrowserAction { const tunnel = model.forwarded.has(arg.remote) ? model.forwarded.get(arg.remote) : model.published.get(arg.remote); let address: string | undefined; if (tunnel && tunnel.localAddress && (address = model.address(tunnel.remote))) { - return openerService.open(URI.parse(address)); + return openerService.open(URI.parse('http://' + address)); } return Promise.resolve(); } diff --git a/src/vs/workbench/services/remote/common/remoteExplorerService.ts b/src/vs/workbench/services/remote/common/remoteExplorerService.ts index f43ba81ec1e..631de089101 100644 --- a/src/vs/workbench/services/remote/common/remoteExplorerService.ts +++ b/src/vs/workbench/services/remote/common/remoteExplorerService.ts @@ -115,9 +115,9 @@ export interface IRemoteExplorerService { targetType: string; readonly helpInformation: HelpInformation[]; readonly tunnelModel: TunnelModel; - onDidChangeEditable: Event; - setEditable(remote: number, data: IEditableData | null): void; - getEditableData(remote: number): IEditableData | undefined; + onDidChangeEditable: Event; + setEditable(remote: number | undefined, data: IEditableData | null): void; + getEditableData(remote: number | undefined): IEditableData | undefined; } export interface HelpInformation { @@ -162,9 +162,9 @@ class RemoteExplorerService implements IRemoteExplorerService { public readonly onDidChangeTargetType: Event = this._onDidChangeTargetType.event; private _helpInformation: HelpInformation[] = []; private _tunnelModel: TunnelModel; - private editable: { remote: number, data: IEditableData } | undefined; - private readonly _onDidChangeEditable: Emitter = new Emitter(); - public readonly onDidChangeEditable: Event = this._onDidChangeEditable.event; + private editable: { remote: number | undefined, data: IEditableData } | undefined; + private readonly _onDidChangeEditable: Emitter = new Emitter(); + public readonly onDidChangeEditable: Event = this._onDidChangeEditable.event; constructor( @IStorageService private readonly storageService: IStorageService, @@ -219,7 +219,7 @@ class RemoteExplorerService implements IRemoteExplorerService { return this._tunnelModel; } - setEditable(remote: number, data: IEditableData | null): void { + setEditable(remote: number | undefined, data: IEditableData | null): void { if (!data) { this.editable = undefined; } else { @@ -228,7 +228,7 @@ class RemoteExplorerService implements IRemoteExplorerService { this._onDidChangeEditable.fire(remote); } - getEditableData(remote: number): IEditableData | undefined { + getEditableData(remote: number | undefined): IEditableData | undefined { return this.editable && this.editable.remote === remote ? this.editable.data : undefined; } } From 8ab4d328fa267aa6ac9a744ae49b61b74c3484d8 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 2 Dec 2019 14:33:43 +0100 Subject: [PATCH 069/255] fixes #85039 --- src/vs/base/browser/dom.ts | 5 ++ src/vs/base/browser/ui/iconLabel/iconLabel.ts | 11 +-- src/vs/base/browser/ui/list/listWidget.ts | 30 ++++++-- src/vs/base/browser/ui/tree/abstractTree.ts | 6 +- src/vs/base/browser/ui/tree/asyncDataTree.ts | 9 ++- src/vs/workbench/browser/labels.ts | 3 +- .../files/browser/views/explorerView.ts | 4 +- .../files/browser/views/explorerViewer.ts | 68 ++++++++++++++----- 8 files changed, 104 insertions(+), 32 deletions(-) diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index e8a190d2a7d..7a582b4e018 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -1079,6 +1079,11 @@ function _$(namespace: Namespace, description: string, attrs? Object.keys(attrs).forEach(name => { const value = attrs![name]; + + if (typeof value === 'undefined') { + return; + } + if (/^on\w+$/.test(name)) { (result)[name] = value; } else if (name === 'selected') { diff --git a/src/vs/base/browser/ui/iconLabel/iconLabel.ts b/src/vs/base/browser/ui/iconLabel/iconLabel.ts index 40e7ae8790f..425f657432c 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabel.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabel.ts @@ -26,6 +26,7 @@ export interface IIconLabelValueOptions { labelEscapeNewLines?: boolean; descriptionMatches?: IMatch[]; readonly separator?: string; + readonly domId?: string; } class FastLabelNode { @@ -178,7 +179,7 @@ class Label { if (!this.singleLabel) { this.container.innerHTML = ''; dom.removeClass(this.container, 'multiple'); - this.singleLabel = dom.append(this.container, dom.$('a.label-name')); + this.singleLabel = dom.append(this.container, dom.$('a.label-name', { id: options?.domId })); } this.singleLabel.textContent = label; @@ -189,8 +190,9 @@ class Label { for (let i = 0; i < label.length; i++) { const l = label[i]; + const id = options?.domId && `${options?.domId}_${i}`; - dom.append(this.container, dom.$('a.label-name', { 'data-icon-label-count': label.length, 'data-icon-label-index': i }, l)); + dom.append(this.container, dom.$('a.label-name', { id, 'data-icon-label-count': label.length, 'data-icon-label-index': i }, l)); if (i < label.length - 1) { dom.append(this.container, dom.$('span.label-separator', undefined, options?.separator || '/')); @@ -238,7 +240,7 @@ class LabelWithHighlights { if (!this.singleLabel) { this.container.innerHTML = ''; dom.removeClass(this.container, 'multiple'); - this.singleLabel = new HighlightedLabel(dom.append(this.container, dom.$('a.label-name')), this.supportCodicons); + this.singleLabel = new HighlightedLabel(dom.append(this.container, dom.$('a.label-name', { id: options?.domId })), this.supportCodicons); } this.singleLabel.set(label, options?.matches, options?.title, options?.labelEscapeNewLines); @@ -254,8 +256,9 @@ class LabelWithHighlights { for (let i = 0; i < label.length; i++) { const l = label[i]; const m = matches ? matches[i] : undefined; + const id = options?.domId && `${options?.domId}_${i}`; - const name = dom.$('a.label-name', { 'data-icon-label-count': label.length, 'data-icon-label-index': i }); + const name = dom.$('a.label-name', { id, 'data-icon-label-count': label.length, 'data-icon-label-index': i }); const highlightedLabel = new HighlightedLabel(dom.append(this.container, name), this.supportCodicons); highlightedLabel.set(l, m, options?.title, options?.labelEscapeNewLines); diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 417998fd379..026344c5e1b 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -702,6 +702,9 @@ export interface IAccessibilityProvider { * https://www.w3.org/TR/wai-aria/#aria-level */ getAriaLevel?(element: T): number | undefined; + + onDidChangeActiveDescendant?: Event; + getActiveDescendantId?(element: T): string | undefined; } export class DefaultStyleController implements IStyleController { @@ -1115,6 +1118,7 @@ export class List implements ISpliceable, IDisposable { private spliceable: ISpliceable; private styleController: IStyleController; private typeLabelController?: TypeLabelController; + private accessibilityProvider?: IAccessibilityProvider; protected readonly disposables = new DisposableStore(); @@ -1200,8 +1204,14 @@ export class List implements ISpliceable, IDisposable { const baseRenderers: IListRenderer[] = [this.focus.renderer, this.selection.renderer]; - if (_options.accessibilityProvider) { - baseRenderers.push(new AccessibiltyRenderer(_options.accessibilityProvider)); + this.accessibilityProvider = _options.accessibilityProvider; + + if (this.accessibilityProvider) { + baseRenderers.push(new AccessibiltyRenderer(this.accessibilityProvider)); + + if (this.accessibilityProvider.onDidChangeActiveDescendant) { + this.accessibilityProvider.onDidChangeActiveDescendant(this.onDidChangeActiveDescendant, this, this.disposables); + } } renderers = renderers.map(r => new PipelineRenderer(r.templateId, [...baseRenderers, r])); @@ -1623,14 +1633,24 @@ export class List implements ISpliceable, IDisposable { private _onFocusChange(): void { const focus = this.focus.get(); + DOM.toggleClass(this.view.domNode, 'element-focused', focus.length > 0); + this.onDidChangeActiveDescendant(); + } + + private onDidChangeActiveDescendant(): void { + const focus = this.focus.get(); if (focus.length > 0) { - this.view.domNode.setAttribute('aria-activedescendant', this.view.getElementDomId(focus[0])); + let id: string | undefined; + + if (this.accessibilityProvider?.getActiveDescendantId) { + id = this.accessibilityProvider.getActiveDescendantId(this.view.element(focus[0])); + } + + this.view.domNode.setAttribute('aria-activedescendant', id || this.view.getElementDomId(focus[0])); } else { this.view.domNode.removeAttribute('aria-activedescendant'); } - - DOM.toggleClass(this.view.domNode, 'element-focused', focus.length > 0); } private _onSelectionChange(): void { diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index f735bebf0df..5f40e3d8987 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -161,12 +161,16 @@ function asListOptions(modelProvider: () => ITreeModel { + return options.accessibilityProvider!.getActiveDescendantId!(node.element); + }) }, keyboardNavigationLabelProvider: options.keyboardNavigationLabelProvider && { ...options.keyboardNavigationLabelProvider, diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index d3a9859f0b5..c21a3a80964 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -230,9 +230,16 @@ function asObjectTreeOptions(options?: IAsyncDataTreeOpt } }, accessibilityProvider: options.accessibilityProvider && { + ...options.accessibilityProvider, getAriaLabel(e) { return options.accessibilityProvider!.getAriaLabel(e.element as T); - } + }, + getAriaLevel: options.accessibilityProvider!.getAriaLevel && (node => { + return options.accessibilityProvider!.getAriaLevel!(node.element as T); + }), + getActiveDescendantId: options.accessibilityProvider.getActiveDescendantId && (node => { + return options.accessibilityProvider!.getActiveDescendantId!(node.element as T); + }) }, filter: options.filter && { filter(e, parentVisibility) { diff --git a/src/vs/workbench/browser/labels.ts b/src/vs/workbench/browser/labels.ts index 51d6ed18b7c..4052bcf0d8b 100644 --- a/src/vs/workbench/browser/labels.ts +++ b/src/vs/workbench/browser/labels.ts @@ -444,7 +444,8 @@ class ResourceLabelWidget extends IconLabel { italic: this.options && this.options.italic, matches: this.options && this.options.matches, extraClasses: [], - separator: this.options?.separator + separator: this.options?.separator, + domId: this.options?.domId }; const resource = this.label.resource; diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 5020df769b7..dd98498ed43 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -29,7 +29,7 @@ import { DelayedDragHandler } from 'vs/base/browser/dnd'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IViewletPaneOptions, ViewletPane } from 'vs/workbench/browser/parts/views/paneViewlet'; import { ILabelService } from 'vs/platform/label/common/label'; -import { ExplorerDelegate, ExplorerAccessibilityProvider, ExplorerDataSource, FilesRenderer, ICompressedNavigationController, FilesFilter, FileSorter, FileDragAndDrop, ExplorerCompressionDelegate, isCompressedFolderName } from 'vs/workbench/contrib/files/browser/views/explorerViewer'; +import { ExplorerDelegate, ExplorerDataSource, FilesRenderer, ICompressedNavigationController, FilesFilter, FileSorter, FileDragAndDrop, ExplorerCompressionDelegate, isCompressedFolderName } from 'vs/workbench/contrib/files/browser/views/explorerViewer'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; @@ -353,7 +353,7 @@ export class ExplorerView extends ViewletPane { this.tree = this.instantiationService.createInstance>(WorkbenchCompressibleAsyncDataTree, 'FileExplorer', container, new ExplorerDelegate(), new ExplorerCompressionDelegate(), [this.renderer], this.instantiationService.createInstance(ExplorerDataSource), { compressionEnabled: isCompressionEnabled(), - accessibilityProvider: new ExplorerAccessibilityProvider(), + accessibilityProvider: this.renderer, ariaLabel: nls.localize('treeAriaLabel', "Files Explorer"), identityProvider: { getId: (stat: ExplorerItem) => { diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 9029835954b..0e95974a599 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -46,7 +46,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/workspaces'; import { findValidPasteFileTarget } from 'vs/workbench/contrib/files/browser/fileActions'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; -import { Emitter } from 'vs/base/common/event'; +import { Emitter, Event, EventMultiplexer } from 'vs/base/common/event'; import { ITreeCompressionDelegate } from 'vs/base/browser/ui/tree/asyncDataTree'; import { ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree'; import { ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel'; @@ -120,10 +120,12 @@ export class ExplorerDataSource implements IAsyncDataSource; previous(): void; next(): void; first(): void; @@ -131,7 +133,9 @@ export interface ICompressedNavigationController { setIndex(index: number): void; } -export class CompressedNavigationController implements ICompressedNavigationController { +export class CompressedNavigationController implements ICompressedNavigationController, IDisposable { + + static ID = 0; private _index: number; readonly labels: HTMLElement[]; @@ -139,10 +143,19 @@ export class CompressedNavigationController implements ICompressedNavigationCont get index(): number { return this._index; } get count(): number { return this.items.length; } get current(): ExplorerItem { return this.items[this._index]!; } + get currentId(): string { return `${this.id}_${this.index}`; } - constructor(readonly items: ExplorerItem[], templateData: IFileTemplateData) { + private _onDidChange = new Emitter(); + readonly onDidChange = this._onDidChange.event; + + constructor(private id: string, readonly items: ExplorerItem[], templateData: IFileTemplateData) { this._index = items.length - 1; this.labels = Array.from(templateData.container.querySelectorAll('.label-name')) as HTMLElement[]; + + for (let i = 0; i < items.length; i++) { + this.labels[i].setAttribute('aria-label', items[i].name); + } + DOM.addClass(this.labels[this._index], 'active'); } @@ -186,6 +199,12 @@ export class CompressedNavigationController implements ICompressedNavigationCont DOM.removeClass(this.labels[this._index], 'active'); this._index = index; DOM.addClass(this.labels[this._index], 'active'); + + this._onDidChange.fire(); + } + + dispose(): void { + this._onDidChange.dispose(); } } @@ -195,13 +214,16 @@ export interface IFileTemplateData { container: HTMLElement; } -export class FilesRenderer implements ICompressibleTreeRenderer, IDisposable { +export class FilesRenderer implements ICompressibleTreeRenderer, IAccessibilityProvider, IDisposable { static readonly ID = 'file'; private config: IFilesConfiguration; private configListener: IDisposable; private compressedNavigationControllers = new Map(); + private _onDidChangeActiveDescendant = new EventMultiplexer(); + readonly onDidChangeActiveDescendant = this._onDidChangeActiveDescendant.event; + constructor( private labels: ResourceLabels, private updateWidth: (stat: ExplorerItem) => void, @@ -240,7 +262,7 @@ export class FilesRenderer implements ICompressibleTreeRenderer e.name); - disposables.add(this.renderStat(stat, label, node.filterData, templateData)); + const id = `compressed-explorer_${CompressedNavigationController.ID++}`; - const compressedNavigationController = new CompressedNavigationController(node.element.elements, templateData); + const label = node.element.elements.map(e => e.name); + disposables.add(this.renderStat(stat, label, id, node.filterData, templateData)); + + const compressedNavigationController = new CompressedNavigationController(id, node.element.elements, templateData); + disposables.add(compressedNavigationController); this.compressedNavigationControllers.set(stat, compressedNavigationController); + // accessibility + disposables.add(this._onDidChangeActiveDescendant.add(compressedNavigationController.onDidChange)); + domEvent(templateData.container, 'mousedown')(e => { const result = getIconLabelNameFromHTMLElement(e.target); @@ -277,9 +305,7 @@ export class FilesRenderer implements ICompressibleTreeRenderer { - this.compressedNavigationControllers.delete(stat); - })); + disposables.add(toDisposable(() => this.compressedNavigationControllers.delete(stat))); templateData.elementDisposable = disposables; } @@ -292,7 +318,7 @@ export class FilesRenderer implements ICompressibleTreeRenderer { @@ -412,15 +439,20 @@ export class FilesRenderer implements ICompressibleTreeRenderer { getAriaLabel(element: ExplorerItem): string { return element.name; } + + getActiveDescendantId(stat: ExplorerItem): string | undefined { + const compressedNavigationController = this.compressedNavigationControllers.get(stat); + return compressedNavigationController?.currentId; + } + + dispose(): void { + this.configListener.dispose(); + } } interface CachedParsedExpression { From a1f97981859d42036236fcdf010ffcd08bc97038 Mon Sep 17 00:00:00 2001 From: isidor Date: Mon, 2 Dec 2019 14:54:00 +0100 Subject: [PATCH 070/255] debug: make sure to select configuration when switching workspace folders --- .../contrib/debug/browser/debugConfigurationManager.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index d13bc6c47f4..fcfb9fc251b 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -286,7 +286,8 @@ export class ConfigurationManager implements IConfigurationManager { this.toDispose.push(this.contextService.onDidChangeWorkspaceFolders(() => { this.initLaunches(); - this.selectConfiguration(this.selectedLaunch); + const toSelect = this.selectedLaunch || (this.launches.length > 0 ? this.launches[0] : undefined); + this.selectConfiguration(toSelect); this.setCompoundSchemaValues(); })); this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => { From 8d3a5b6e98c0831d59d10fb5154b9dbb05d7d9f4 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 2 Dec 2019 06:17:21 -0800 Subject: [PATCH 071/255] Sign with prod key --- build/azure-pipelines/linux/product-build-linux.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index 972962dfcff..573d7c7d4c2 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -134,7 +134,7 @@ steps: inlineOperation: | [ { - "keyCode": "CP-450778-Pgp", + "keyCode": "CP-450779-Pgp", "operationSetCode": "LinuxSign", "parameters": [ ], "toolName": "sign", From 3e27bb06a37500635ad56e00bc0ae24fb16c63cd Mon Sep 17 00:00:00 2001 From: isidor Date: Mon, 2 Dec 2019 15:35:04 +0100 Subject: [PATCH 072/255] Errors from evaluateRequest should be red in the debug console fixes #83668 --- .../contrib/debug/browser/baseDebugView.ts | 3 ++- src/vs/workbench/contrib/debug/common/debugModel.ts | 5 +++++ src/vs/workbench/contrib/debug/common/replModel.ts | 13 +++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts index 233a2a43a92..9acef112583 100644 --- a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts +++ b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts @@ -17,6 +17,7 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { HighlightedLabel, IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; +import { ReplEvaluationResult } from 'vs/workbench/contrib/debug/common/replModel'; export const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024; export const twistiePixels = 20; @@ -58,7 +59,7 @@ export function renderExpressionValue(expressionOrValue: IExpressionContainer | // remove stale classes container.className = 'value'; // when resolving expressions we represent errors from the server as a variable with name === null. - if (value === null || ((expressionOrValue instanceof Expression || expressionOrValue instanceof Variable) && !expressionOrValue.available)) { + if (value === null || ((expressionOrValue instanceof Expression || expressionOrValue instanceof Variable || expressionOrValue instanceof ReplEvaluationResult) && !expressionOrValue.available)) { dom.addClass(container, 'unavailable'); if (value !== Expression.DEFAULT_VALUE) { dom.addClass(container, 'error'); diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index 380ee08754b..311ac19dcf7 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -157,6 +157,11 @@ export class ExpressionContainer implements IExpressionContainer { this.session = session; try { const response = await session.evaluate(expression, stackFrame ? stackFrame.frameId : undefined, context); + if (response && response.success === false) { + this.value = response.message || ''; + return false; + } + if (response && response.body) { this.value = response.body.result || ''; this.reference = response.body.variablesReference; diff --git a/src/vs/workbench/contrib/debug/common/replModel.ts b/src/vs/workbench/contrib/debug/common/replModel.ts index bc0f77b86d2..f9c8ae7c43b 100644 --- a/src/vs/workbench/contrib/debug/common/replModel.ts +++ b/src/vs/workbench/contrib/debug/common/replModel.ts @@ -98,10 +98,23 @@ export class ReplEvaluationInput implements IReplElement { } export class ReplEvaluationResult extends ExpressionContainer implements IReplElement { + private _available = true; + + get available(): boolean { + return this._available; + } + constructor() { super(undefined, undefined, 0, generateUuid()); } + async evaluateExpression(expression: string, session: IDebugSession | undefined, stackFrame: IStackFrame | undefined, context: string): Promise { + const result = await super.evaluateExpression(expression, session, stackFrame, context); + this._available = result; + + return result; + } + toString(): string { return `${this.value}`; } From 099e498f9e6219a1e7c9c1c2697c7a51a807e649 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 2 Dec 2019 15:40:38 +0100 Subject: [PATCH 073/255] update references view extension --- build/builtInExtensions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/builtInExtensions.json b/build/builtInExtensions.json index ec97bf7bc5e..f5c1750229c 100644 --- a/build/builtInExtensions.json +++ b/build/builtInExtensions.json @@ -31,7 +31,7 @@ }, { "name": "ms-vscode.references-view", - "version": "0.0.38", + "version": "0.0.39", "repo": "https://github.com/Microsoft/vscode-reference-view", "metadata": { "id": "dc489f46-520d-4556-ae85-1f9eab3c412d", From 8a7928e2707c73066bd3ffca33b323e9bed45e65 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 2 Dec 2019 15:48:58 +0100 Subject: [PATCH 074/255] Fix #85210 --- .../sharedProcess/sharedProcessMain.ts | 3 +- .../userDataSync/common/userDataAutoSync.ts | 69 +++++++++++++++++++ .../common/userDataSyncService.ts | 66 +----------------- .../electron-browser/userDataAutoSync.ts | 32 +++++++++ .../userDataSync/browser/userDataAutoSync.ts | 35 ++++++++++ .../userDataSync/browser/userDataSync.ts | 13 +++- .../browser/userDataSyncTrigger.ts | 61 ++++++++++++++++ 7 files changed, 212 insertions(+), 67 deletions(-) create mode 100644 src/vs/platform/userDataSync/common/userDataAutoSync.ts create mode 100644 src/vs/platform/userDataSync/electron-browser/userDataAutoSync.ts create mode 100644 src/vs/workbench/contrib/userDataSync/browser/userDataAutoSync.ts create mode 100644 src/vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger.ts diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 27469c14739..96ce984b782 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -51,7 +51,7 @@ import { DiskFileSystemProvider } from 'vs/platform/files/electron-browser/diskF import { Schemas } from 'vs/base/common/network'; import { IProductService } from 'vs/platform/product/common/productService'; import { IUserDataSyncService, IUserDataSyncStoreService, ISettingsMergeService, registerConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; -import { UserDataSyncService, UserDataAutoSync } from 'vs/platform/userDataSync/common/userDataSyncService'; +import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; import { UserDataSyncChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc'; import { SettingsMergeChannelClient } from 'vs/platform/userDataSync/common/settingsSyncIpc'; @@ -64,6 +64,7 @@ import { AuthTokenChannel } from 'vs/platform/auth/common/authTokenIpc'; import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; import { KeytarCredentialsService } from 'vs/platform/credentials/node/credentialsService'; import { UserDataSyncUtilServiceClient } from 'vs/platform/userDataSync/common/keybindingsSyncIpc'; +import { UserDataAutoSync } from 'vs/platform/userDataSync/electron-browser/userDataAutoSync'; export interface ISharedProcessConfiguration { readonly machineId: string; diff --git a/src/vs/platform/userDataSync/common/userDataAutoSync.ts b/src/vs/platform/userDataSync/common/userDataAutoSync.ts new file mode 100644 index 00000000000..75123459141 --- /dev/null +++ b/src/vs/platform/userDataSync/common/userDataAutoSync.ts @@ -0,0 +1,69 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IUserDataSyncService, SyncStatus, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { Event } from 'vs/base/common/event'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { timeout } from 'vs/base/common/async'; +import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth'; + +export class UserDataAutoSync extends Disposable { + + private enabled: boolean = false; + + constructor( + @IConfigurationService private readonly configurationService: IConfigurationService, + @IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService, + @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, + @IAuthTokenService private readonly authTokenService: IAuthTokenService, + ) { + super(); + this.updateEnablement(false); + this._register(Event.any(authTokenService.onDidChangeStatus, userDataSyncService.onDidChangeStatus)(() => this.updateEnablement(true))); + this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('sync.enable'))(() => this.updateEnablement(true))); + } + + private updateEnablement(stopIfDisabled: boolean): void { + const enabled = this.isSyncEnabled(); + if (this.enabled === enabled) { + return; + } + + this.enabled = enabled; + if (this.enabled) { + this.logService.info('Syncing configuration started'); + this.sync(true); + return; + } else { + if (stopIfDisabled) { + this.userDataSyncService.stop(); + this.logService.info('Syncing configuration stopped.'); + } + } + + } + + protected async sync(loop: boolean): Promise { + if (this.enabled) { + try { + await this.userDataSyncService.sync(); + } catch (e) { + this.logService.error(e); + } + if (loop) { + await timeout(1000 * 60 * 5); // Loop sync for every 5 min. + this.sync(loop); + } + } + } + + private isSyncEnabled(): boolean { + return this.configurationService.getValue('sync.enable') + && this.userDataSyncService.status !== SyncStatus.Uninitialized + && this.authTokenService.status === AuthTokenStatus.SignedIn; + } + +} diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index d4a1b83deda..db118026b58 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -3,13 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserDataSyncService, SyncStatus, ISynchroniser, IUserDataSyncStoreService, SyncSource, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, SyncStatus, ISynchroniser, IUserDataSyncStoreService, SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; import { Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync'; import { Emitter, Event } from 'vs/base/common/event'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { timeout } from 'vs/base/common/async'; import { ExtensionsSynchroniser } from 'vs/platform/userDataSync/common/extensionsSync'; import { IExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth'; @@ -128,65 +126,3 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } } - -export class UserDataAutoSync extends Disposable { - - private enabled: boolean = false; - - constructor( - @IConfigurationService private readonly configurationService: IConfigurationService, - @IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService, - @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, - @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, - @IAuthTokenService private readonly authTokenService: IAuthTokenService, - ) { - super(); - this.updateEnablement(false); - this._register(Event.any(authTokenService.onDidChangeStatus, userDataSyncService.onDidChangeStatus)(() => this.updateEnablement(true))); - this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('sync.enable'))(() => this.updateEnablement(true))); - - // Sync immediately if there is a local change. - this._register(Event.debounce(this.userDataSyncService.onDidChangeLocal, () => undefined, 500)(() => this.sync(false))); - } - - private updateEnablement(stopIfDisabled: boolean): void { - const enabled = this.isSyncEnabled(); - if (this.enabled === enabled) { - return; - } - - this.enabled = enabled; - if (this.enabled) { - this.logService.info('Syncing configuration started'); - this.sync(true); - return; - } else { - if (stopIfDisabled) { - this.userDataSyncService.stop(); - this.logService.info('Syncing configuration stopped.'); - } - } - - } - - private async sync(loop: boolean): Promise { - if (this.enabled) { - try { - await this.userDataSyncService.sync(); - } catch (e) { - this.logService.error(e); - } - if (loop) { - await timeout(1000 * 30); // Loop sync for every 30s. - this.sync(loop); - } - } - } - - private isSyncEnabled(): boolean { - return this.configurationService.getValue('sync.enable') - && this.userDataSyncService.status !== SyncStatus.Uninitialized - && this.authTokenService.status === AuthTokenStatus.SignedIn; - } - -} diff --git a/src/vs/platform/userDataSync/electron-browser/userDataAutoSync.ts b/src/vs/platform/userDataSync/electron-browser/userDataAutoSync.ts new file mode 100644 index 00000000000..90ec9533ecd --- /dev/null +++ b/src/vs/platform/userDataSync/electron-browser/userDataAutoSync.ts @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IUserDataSyncService, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync'; +import { Event } from 'vs/base/common/event'; +import { IElectronService } from 'vs/platform/electron/node/electron'; +import { UserDataAutoSync as BaseUserDataAutoSync } from 'vs/platform/userDataSync/common/userDataAutoSync'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IAuthTokenService } from 'vs/platform/auth/common/auth'; + +export class UserDataAutoSync extends BaseUserDataAutoSync { + + constructor( + @IUserDataSyncService userDataSyncService: IUserDataSyncService, + @IElectronService electronService: IElectronService, + @IConfigurationService configurationService: IConfigurationService, + @IUserDataSyncLogService logService: IUserDataSyncLogService, + @IAuthTokenService authTokenService: IAuthTokenService, + ) { + super(configurationService, userDataSyncService, logService, authTokenService); + + // Sync immediately if there is a local change. + this._register(Event.debounce(Event.any( + electronService.onWindowFocus, + electronService.onWindowOpen, + userDataSyncService.onDidChangeLocal + ), () => undefined, 500)(() => this.sync(false))); + } + +} diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataAutoSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataAutoSync.ts new file mode 100644 index 00000000000..d0d54c3c403 --- /dev/null +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataAutoSync.ts @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IUserDataSyncService, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IAuthTokenService } from 'vs/platform/auth/common/auth'; +import { Event } from 'vs/base/common/event'; +import { UserDataAutoSync as BaseUserDataAutoSync } from 'vs/platform/userDataSync/common/userDataAutoSync'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { UserDataSyncTrigger } from 'vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; + +export class UserDataAutoSync extends BaseUserDataAutoSync { + + constructor( + @IUserDataSyncService userDataSyncService: IUserDataSyncService, + @IConfigurationService configurationService: IConfigurationService, + @IUserDataSyncLogService logService: IUserDataSyncLogService, + @IAuthTokenService authTokenService: IAuthTokenService, + @IInstantiationService instantiationService: IInstantiationService, + @IHostService hostService: IHostService, + ) { + super(configurationService, userDataSyncService, logService, authTokenService); + + // Sync immediately if there is a local change. + this._register(Event.debounce(Event.any( + userDataSyncService.onDidChangeLocal, + instantiationService.createInstance(UserDataSyncTrigger).onDidTriggerSync, + hostService.onDidChangeFocus + ), () => undefined, 500)(() => this.sync(false))); + } + +} diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 1f5577d1189..2a57564f862 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -30,7 +30,8 @@ import { FalseContext } from 'vs/platform/contextkey/common/contextkeys'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { isWeb } from 'vs/base/common/platform'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { UserDataAutoSync } from 'vs/platform/userDataSync/common/userDataSyncService'; +import { UserDataAutoSync } from 'vs/workbench/contrib/userDataSync/browser/userDataAutoSync'; +import { UserDataSyncTrigger } from 'vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger'; const CONTEXT_AUTH_TOKEN_STATE = new RawContextKey('authTokenStatus', AuthTokenStatus.Initializing); const SYNC_PUSH_LIGHT_ICON_URI = URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/userDataSync/browser/media/check-light.svg`)); @@ -78,10 +79,20 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo if (isWeb) { this._register(instantiationService.createInstance(UserDataAutoSync)); + } else { + this._register(instantiationService.createInstance(UserDataSyncTrigger).onDidTriggerSync(() => this.triggerSync())); } } } + private triggerSync(): void { + if (this.configurationService.getValue('sync.enable') + && this.userDataSyncService.status !== SyncStatus.Uninitialized + && this.authTokenService.status === AuthTokenStatus.SignedIn) { + this.userDataSyncService.sync(); + } + } + private onDidChangeAuthTokenStatus(status: AuthTokenStatus) { this.authTokenContext.set(status); if (status === AuthTokenStatus.SignedIn) { diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger.ts new file mode 100644 index 00000000000..21dc9c85367 --- /dev/null +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger.ts @@ -0,0 +1,61 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event, Emitter } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { SettingsEditor2Input, KeybindingsEditorInput, PreferencesEditorInput } from 'vs/workbench/services/preferences/common/preferencesEditorInput'; +import { isEqual } from 'vs/base/common/resources'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; +import { VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions'; +import { IEditorInput } from 'vs/workbench/common/editor'; +import { IViewlet } from 'vs/workbench/common/viewlet'; + +export class UserDataSyncTrigger extends Disposable { + + private readonly _onDidTriggerSync: Emitter = this._register(new Emitter()); + readonly onDidTriggerSync: Event = this._onDidTriggerSync.event; + + constructor( + @IEditorService editorService: IEditorService, + @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService, + @IViewletService viewletService: IViewletService, + ) { + super(); + this._register(Event.debounce(Event.any( + Event.filter(editorService.onDidActiveEditorChange, () => this.isUserDataEditorInput(editorService.activeEditor)), + Event.filter(viewletService.onDidViewletOpen, viewlet => this.isUserDataViewlet(viewlet)) + ), () => undefined, 500)(() => this._onDidTriggerSync.fire())); + } + + private isUserDataViewlet(viewlet: IViewlet): boolean { + return viewlet.getId() === VIEWLET_ID; + } + + private isUserDataEditorInput(editorInput: IEditorInput | undefined): boolean { + if (!editorInput) { + return false; + } + if (editorInput instanceof SettingsEditor2Input) { + return true; + } + if (editorInput instanceof PreferencesEditorInput) { + return true; + } + if (editorInput instanceof KeybindingsEditorInput) { + return true; + } + const resource = editorInput.getResource(); + if (isEqual(resource, this.workbenchEnvironmentService.settingsResource)) { + return true; + } + if (isEqual(resource, this.workbenchEnvironmentService.keybindingsResource)) { + return true; + } + return false; + } +} + From e7a7e46fcc0a590f16d99df3ea08a8f35c180c14 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 2 Dec 2019 15:57:02 +0100 Subject: [PATCH 075/255] add another unit test for #80688 --- .../src/singlefolder-tests/workspace.test.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts index 175ef8522d1..6e409489af6 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts @@ -916,4 +916,23 @@ suite('workspace-namespace', () => { const expected2 = 'import2;import1;'; assert.equal(document.getText(), expected2); }); + + test('The api workspace.applyEdit failed for some case of mixing resourceChange and textEdit #80688', async function () { + const file1 = await createRandomFile(); + const file2 = await createRandomFile(); + let we = new vscode.WorkspaceEdit(); + we.insert(file1, new vscode.Position(0, 0), 'import1;'); + we.insert(file1, new vscode.Position(0, 0), 'import2;'); + + const file2Name = basename(file2.fsPath); + const file2NewUri = vscode.Uri.parse(file2.toString().replace(file2Name, `new/${file2Name}`)); + we.renameFile(file2, file2NewUri); + + await vscode.workspace.applyEdit(we); + + const document = await vscode.workspace.openTextDocument(file1); + const expected = 'import1;import2;'; + // const expected2 = 'import2;import1;'; + assert.equal(document.getText(), expected); + }); }); From a01739bab2eccc05ae0153fdc1410be1b6b8e92c Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 2 Dec 2019 15:58:51 +0100 Subject: [PATCH 076/255] Fix #85351 --- .../contrib/userDataSync/browser/userDataSync.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 2a57564f862..9e702a131d6 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -32,6 +32,7 @@ import { isWeb } from 'vs/base/common/platform'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { UserDataAutoSync } from 'vs/workbench/contrib/userDataSync/browser/userDataAutoSync'; import { UserDataSyncTrigger } from 'vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger'; +import { timeout } from 'vs/base/common/async'; const CONTEXT_AUTH_TOKEN_STATE = new RawContextKey('authTokenStatus', AuthTokenStatus.Initializing); const SYNC_PUSH_LIGHT_ICON_URI = URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/userDataSync/browser/media/check-light.svg`)); @@ -104,7 +105,12 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo private onDidChangeSyncStatus(status: SyncStatus) { this.syncStatusContext.set(status); - this.updateBadge(); + if (status === SyncStatus.Syncing) { + // Show syncing progress if takes more than 1s. + timeout(1000).then(() => this.updateBadge()); + } else { + this.updateBadge(); + } if (this.userDataSyncService.status === SyncStatus.HasConflicts) { if (!this.conflictsWarningDisposable.value) { From 8d2160686e73b3ea2fd5d44628f4b7348e6aeff4 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 2 Dec 2019 16:01:09 +0100 Subject: [PATCH 077/255] add jsdoc comment for #80688 --- src/vs/vscode.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 060cd82e81e..04396f195a3 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -8315,8 +8315,8 @@ declare module 'vscode' { * * All changes of a workspace edit are applied in the same order in which they have been added. If * multiple textual inserts are made at the same position, these strings appear in the resulting text - * in the order the 'inserts' were made. Invalid sequences like 'delete file a' -> 'insert text in file a' - * cause failure of the operation. + * in the order the 'inserts' were made, unless that are interleaved with resource edits. Invalid sequences + * like 'delete file a' -> 'insert text in file a' cause failure of the operation. * * When applying a workspace edit that consists only of text edits an 'all-or-nothing'-strategy is used. * A workspace edit with resource creations or deletions aborts the operation, e.g. consecutive edits will From d0c8461d5965cd269f0f1787e3d72473bd76e666 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 2 Dec 2019 16:09:40 +0100 Subject: [PATCH 078/255] skip tests in all platforms --- .../test/common/keybindingsMerge.test.ts | 198 +++++++++--------- 1 file changed, 98 insertions(+), 100 deletions(-) diff --git a/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts b/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts index c1a38818e55..10970c25bbf 100644 --- a/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts +++ b/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts @@ -440,18 +440,17 @@ suite('KeybindingsMerge - No Conflicts', () => { }); -if (OS !== OperatingSystem.Windows) { - suite('KeybindingsMerge - Conflicts', () => { +suite.skip('KeybindingsMerge - Conflicts', () => { - test('merge when local and remote with one entry but different value', async () => { - const localContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const actual = await mergeKeybindings(localContent, remoteContent, null); - assert.ok(actual.hasChanges); - assert.ok(actual.hasConflicts); - assert.equal(actual.mergeContent, - `<<<<<<< local + test('merge when local and remote with one entry but different value', async () => { + const localContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const actual = await mergeKeybindings(localContent, remoteContent, null); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local [ { "key": "alt+d", @@ -468,22 +467,22 @@ if (OS !== OperatingSystem.Windows) { } ] >>>>>>> remote`); - }); + }); - test('merge when local and remote with different keybinding', async () => { - const localContent = stringify([ - { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, - { key: 'alt+a', command: '-a', when: 'editorTextFocus && !editorReadonly' } - ]); - const remoteContent = stringify([ - { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, - { key: 'alt+a', command: '-a', when: 'editorTextFocus && !editorReadonly' } - ]); - const actual = await mergeKeybindings(localContent, remoteContent, null); - assert.ok(actual.hasChanges); - assert.ok(actual.hasConflicts); - assert.equal(actual.mergeContent, - `<<<<<<< local + test('merge when local and remote with different keybinding', async () => { + const localContent = stringify([ + { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+a', command: '-a', when: 'editorTextFocus && !editorReadonly' } + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+a', command: '-a', when: 'editorTextFocus && !editorReadonly' } + ]); + const actual = await mergeKeybindings(localContent, remoteContent, null); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local [ { "key": "alt+d", @@ -510,17 +509,17 @@ if (OS !== OperatingSystem.Windows) { } ] >>>>>>> remote`); - }); + }); - test('merge when the entry is removed in local but updated in remote', async () => { - const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const localContent = stringify([]); - const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const actual = await mergeKeybindings(localContent, remoteContent, baseContent); - assert.ok(actual.hasChanges); - assert.ok(actual.hasConflicts); - assert.equal(actual.mergeContent, - `<<<<<<< local + test('merge when the entry is removed in local but updated in remote', async () => { + const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const localContent = stringify([]); + const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const actual = await mergeKeybindings(localContent, remoteContent, baseContent); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local [] ======= [ @@ -531,17 +530,17 @@ if (OS !== OperatingSystem.Windows) { } ] >>>>>>> remote`); - }); + }); - test('merge when the entry is removed in local but updated in remote and a new entry is added in local', async () => { - const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const localContent = stringify([{ key: 'alt+b', command: 'b' }]); - const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const actual = await mergeKeybindings(localContent, remoteContent, baseContent); - assert.ok(actual.hasChanges); - assert.ok(actual.hasConflicts); - assert.equal(actual.mergeContent, - `<<<<<<< local + test('merge when the entry is removed in local but updated in remote and a new entry is added in local', async () => { + const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const localContent = stringify([{ key: 'alt+b', command: 'b' }]); + const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const actual = await mergeKeybindings(localContent, remoteContent, baseContent); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local [ { "key": "alt+b", @@ -557,17 +556,17 @@ if (OS !== OperatingSystem.Windows) { } ] >>>>>>> remote`); - }); + }); - test('merge when the entry is removed in remote but updated in local', async () => { - const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const remoteContent = stringify([]); - const actual = await mergeKeybindings(localContent, remoteContent, baseContent); - assert.ok(actual.hasChanges); - assert.ok(actual.hasConflicts); - assert.equal(actual.mergeContent, - `<<<<<<< local + test('merge when the entry is removed in remote but updated in local', async () => { + const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const remoteContent = stringify([]); + const actual = await mergeKeybindings(localContent, remoteContent, baseContent); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local [ { "key": "alt+c", @@ -578,17 +577,17 @@ if (OS !== OperatingSystem.Windows) { ======= [] >>>>>>> remote`); - }); + }); - test('merge when the entry is removed in remote but updated in local and a new entry is added in remote', async () => { - const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); - const remoteContent = stringify([{ key: 'alt+b', command: 'b' }]); - const actual = await mergeKeybindings(localContent, remoteContent, baseContent); - assert.ok(actual.hasChanges); - assert.ok(actual.hasConflicts); - assert.equal(actual.mergeContent, - `<<<<<<< local + test('merge when the entry is removed in remote but updated in local and a new entry is added in remote', async () => { + const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const remoteContent = stringify([{ key: 'alt+b', command: 'b' }]); + const actual = await mergeKeybindings(localContent, remoteContent, baseContent); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local [ { "key": "alt+c", @@ -608,39 +607,39 @@ if (OS !== OperatingSystem.Windows) { } ] >>>>>>> remote`); - }); + }); - test('merge when local and remote has moved forwareded with conflicts', async () => { - const baseContent = stringify([ - { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, - { key: 'alt+c', command: '-a' }, - { key: 'cmd+e', command: 'd' }, - { key: 'alt+a', command: 'f' }, - { key: 'alt+d', command: '-f' }, - { key: 'cmd+d', command: 'c', when: 'context1' }, - { key: 'cmd+c', command: '-c' }, - ]); - const localContent = stringify([ - { key: 'alt+d', command: '-f' }, - { key: 'cmd+e', command: 'd' }, - { key: 'cmd+c', command: '-c' }, - { key: 'cmd+d', command: 'c', when: 'context1' }, - { key: 'alt+a', command: 'f' }, - { key: 'alt+e', command: 'e' }, - ]); - const remoteContent = stringify([ - { key: 'alt+a', command: 'f' }, - { key: 'cmd+c', command: '-c' }, - { key: 'cmd+d', command: 'd' }, - { key: 'alt+d', command: '-f' }, - { key: 'alt+c', command: 'c', when: 'context1' }, - { key: 'alt+g', command: 'g', when: 'context2' }, - ]); - const actual = await mergeKeybindings(localContent, remoteContent, baseContent); - assert.ok(actual.hasChanges); - assert.ok(actual.hasConflicts); - assert.equal(actual.mergeContent, - `<<<<<<< local + test('merge when local and remote has moved forwareded with conflicts', async () => { + const baseContent = stringify([ + { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+c', command: '-a' }, + { key: 'cmd+e', command: 'd' }, + { key: 'alt+a', command: 'f' }, + { key: 'alt+d', command: '-f' }, + { key: 'cmd+d', command: 'c', when: 'context1' }, + { key: 'cmd+c', command: '-c' }, + ]); + const localContent = stringify([ + { key: 'alt+d', command: '-f' }, + { key: 'cmd+e', command: 'd' }, + { key: 'cmd+c', command: '-c' }, + { key: 'cmd+d', command: 'c', when: 'context1' }, + { key: 'alt+a', command: 'f' }, + { key: 'alt+e', command: 'e' }, + ]); + const remoteContent = stringify([ + { key: 'alt+a', command: 'f' }, + { key: 'cmd+c', command: '-c' }, + { key: 'cmd+d', command: 'd' }, + { key: 'alt+d', command: '-f' }, + { key: 'alt+c', command: 'c', when: 'context1' }, + { key: 'alt+g', command: 'g', when: 'context2' }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, baseContent); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local [ { "key": "alt+d", @@ -703,11 +702,10 @@ if (OS !== OperatingSystem.Windows) { } ] >>>>>>> remote`); - }); - }); -} +}); + async function mergeKeybindings(localContent: string, remoteContent: string, baseContent: string | null) { const userDataSyncUtilService = new MockUserDataSyncUtilService(); From c723666fb0df256a1def31b413c457be2adaa7ad Mon Sep 17 00:00:00 2001 From: isidor Date: Mon, 2 Dec 2019 16:28:22 +0100 Subject: [PATCH 079/255] Clearer indication of what is input and what is output in debug console fixes #37363 --- src/vs/workbench/contrib/debug/browser/media/repl.css | 6 ++++++ src/vs/workbench/contrib/debug/browser/repl.ts | 2 ++ 2 files changed, 8 insertions(+) diff --git a/src/vs/workbench/contrib/debug/browser/media/repl.css b/src/vs/workbench/contrib/debug/browser/media/repl.css index 68c1f96bacc..ecec955ca92 100644 --- a/src/vs/workbench/contrib/debug/browser/media/repl.css +++ b/src/vs/workbench/contrib/debug/browser/media/repl.css @@ -39,6 +39,12 @@ flex: 1; } +.repl .repl-tree .monaco-tl-contents .arrow { + position:absolute; + left: 2px; + opacity: 0.3; +} + .repl .repl-tree .output.expression.value-and-source .source { margin-left: 4px; margin-right: 8px; diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index 4ff31849ccd..df3ba1ceef0 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -610,6 +610,7 @@ class ReplEvaluationInputsRenderer implements ITreeRenderer Date: Mon, 2 Dec 2019 16:29:20 +0100 Subject: [PATCH 080/255] Fix #85052 --- src/vs/platform/userDataSync/common/extensionsSync.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index 720d0d86ccc..502384a6731 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -166,7 +166,7 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser // First time sync if (!remoteExtensions) { this.logService.info('Extensions: Remote extensions does not exist. Synchronizing extensions for the first time.'); - return { added: [], removed: [], updated: [], remote: localExtensions.filter(({ identifier }) => ignoredExtensions.some(id => id.toLowerCase() === identifier.id.toLowerCase())) }; + return { added: [], removed: [], updated: [], remote: localExtensions.filter(({ identifier }) => ignoredExtensions.every(id => id.toLowerCase() !== identifier.id.toLowerCase())) }; } const uuids: Map = new Map(); From 6a92fcc7a65b7d1f71d7e09374b462b6f1eac11d Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 2 Dec 2019 16:30:59 +0100 Subject: [PATCH 081/255] fix tests --- .../platform/userDataSync/test/common/keybindingsMerge.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts b/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts index 10970c25bbf..85ad7a36b13 100644 --- a/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts +++ b/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts @@ -714,8 +714,7 @@ async function mergeKeybindings(localContent: string, remoteContent: string, bas } function stringify(value: any): string { - const result = JSON.stringify(value, null, '\t'); - return OS === OperatingSystem.Windows ? result.replace('\n', '\r\n') : result; + return JSON.stringify(value, null, '\t'); } class MockUserDataSyncUtilService implements IUserDataSyncUtilService { From 7ac8b2a8a0dec30995df6a0f1108c1ec47eca8ea Mon Sep 17 00:00:00 2001 From: isidor Date: Mon, 2 Dec 2019 16:31:50 +0100 Subject: [PATCH 082/255] repl: stronger arros in dark theme --- src/vs/workbench/contrib/debug/browser/media/repl.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/workbench/contrib/debug/browser/media/repl.css b/src/vs/workbench/contrib/debug/browser/media/repl.css index ecec955ca92..96b7e52d65d 100644 --- a/src/vs/workbench/contrib/debug/browser/media/repl.css +++ b/src/vs/workbench/contrib/debug/browser/media/repl.css @@ -45,6 +45,10 @@ opacity: 0.3; } +.vs-dark .repl .repl-tree .monaco-tl-contents .arrow { + opacity: 0.45; +} + .repl .repl-tree .output.expression.value-and-source .source { margin-left: 4px; margin-right: 8px; From 350e8d2bfd20f8546895e8ecfc81eb5f14db89fa Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 2 Dec 2019 16:35:46 +0100 Subject: [PATCH 083/255] for now, disable outline navigation #46153 --- .../workbench/contrib/outline/browser/outline.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/outline/browser/outline.contribution.ts b/src/vs/workbench/contrib/outline/browser/outline.contribution.ts index 37462197013..33ab1f4a348 100644 --- a/src/vs/workbench/contrib/outline/browser/outline.contribution.ts +++ b/src/vs/workbench/contrib/outline/browser/outline.contribution.ts @@ -11,7 +11,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import { OutlineConfigKeys, OutlineViewId } from 'vs/editor/contrib/documentSymbols/outline'; -import './outlineNavigation'; +// import './outlineNavigation'; const _outlineDesc = { id: OutlineViewId, From f4fa05e0aecdb5b2eb658686df95e00c2d06a079 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 2 Dec 2019 16:41:51 +0100 Subject: [PATCH 084/255] fix typo --- src/vs/vscode.proposed.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 5d2f4c6e87c..e9cc058941c 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -825,7 +825,7 @@ declare module 'vscode' { readonly creationOptions: Readonly; } - //#endregionn + //#endregion //#region Terminal dimensions property and change event https://github.com/microsoft/vscode/issues/55718 From 888c06f60542c829d567e43721a8174c5351a03f Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 2 Dec 2019 18:38:10 +0100 Subject: [PATCH 085/255] Change log name to Sync --- src/vs/workbench/contrib/logs/common/logs.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/logs/common/logs.contribution.ts b/src/vs/workbench/contrib/logs/common/logs.contribution.ts index 7a1a767f67a..70a088ed9f8 100644 --- a/src/vs/workbench/contrib/logs/common/logs.contribution.ts +++ b/src/vs/workbench/contrib/logs/common/logs.contribution.ts @@ -45,7 +45,7 @@ class LogOutputChannels extends Disposable implements IWorkbenchContribution { } private registerCommonContributions(): void { - this.registerLogChannel(Constants.userDataSyncLogChannelId, nls.localize('userDataSyncLog', "Configuration Sync"), this.environmentService.userDataSyncLogResource); + this.registerLogChannel(Constants.userDataSyncLogChannelId, nls.localize('userDataSyncLog', "Sync"), this.environmentService.userDataSyncLogResource); this.registerLogChannel(Constants.rendererLogChannelId, nls.localize('rendererLog', "Window"), this.environmentService.logFile); } From 573df438dbb97795ea6ce3c242c27f0dcd8b43dc Mon Sep 17 00:00:00 2001 From: Rachel Macfarlane Date: Mon, 2 Dec 2019 10:41:08 -0800 Subject: [PATCH 086/255] Add minimap.errorHighlight and minimap.warningHighlight theme colors, #82291 --- .../editor/common/services/markerDecorationsServiceImpl.ts | 5 +++-- src/vs/platform/theme/common/colorRegistry.ts | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/common/services/markerDecorationsServiceImpl.ts b/src/vs/editor/common/services/markerDecorationsServiceImpl.ts index fbf9037fa91..44fd54c6c6d 100644 --- a/src/vs/editor/common/services/markerDecorationsServiceImpl.ts +++ b/src/vs/editor/common/services/markerDecorationsServiceImpl.ts @@ -17,6 +17,7 @@ import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDeco import { Schemas } from 'vs/base/common/network'; import { Emitter, Event } from 'vs/base/common/event'; import { withUndefinedAsNull } from 'vs/base/common/types'; +import { minimapWarning, minimapError } from 'vs/platform/theme/common/colorRegistry'; function MODEL_ID(resource: URI): string { return resource.toString(); @@ -205,7 +206,7 @@ export class MarkerDecorationsService extends Disposable implements IMarkerDecor color = themeColorFromId(overviewRulerWarning); zIndex = 20; minimap = { - color, + color: themeColorFromId(minimapWarning), position: MinimapPosition.Inline }; break; @@ -220,7 +221,7 @@ export class MarkerDecorationsService extends Disposable implements IMarkerDecor color = themeColorFromId(overviewRulerError); zIndex = 30; minimap = { - color, + color: themeColorFromId(minimapError), position: MinimapPosition.Inline }; break; diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index 864db693fd1..43163b757bc 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -422,6 +422,8 @@ export const overviewRulerSelectionHighlightForeground = registerColor('editorOv export const minimapFindMatch = registerColor('minimap.findMatchHighlight', { light: '#d18616', dark: '#d18616', hc: '#AB5A00' }, nls.localize('minimapFindMatchHighlight', 'Minimap marker color for find matches.'), true); export const minimapSelection = registerColor('minimap.selectionHighlight', { light: '#ADD6FF', dark: '#264F78', hc: '#ffffff' }, nls.localize('minimapSelectionHighlight', 'Minimap marker color for the editor selection.'), true); +export const minimapError = registerColor('minimap.errorHighlight', { dark: new Color(new RGBA(255, 18, 18, 0.7)), light: new Color(new RGBA(255, 18, 18, 0.7)), hc: new Color(new RGBA(255, 50, 50, 1)) }, nls.localize('minimapError', 'Minimap marker color for errors.')); +export const minimapWarning = registerColor('minimap.warningHighlight', { dark: editorWarningForeground, light: editorWarningForeground, hc: editorWarningBorder }, nls.localize('overviewRuleWarning', 'Minimap marker color for warnings.')); export const problemsErrorIconForeground = registerColor('problemsErrorIcon.foreground', { dark: editorErrorForeground, light: editorErrorForeground, hc: editorErrorForeground }, nls.localize('problemsErrorIconForeground', "The color used for the problems error icon.")); export const problemsWarningIconForeground = registerColor('problemsWarningIcon.foreground', { dark: editorWarningForeground, light: editorWarningForeground, hc: editorWarningForeground }, nls.localize('problemsWarningIconForeground', "The color used for the problems warning icon.")); From a1affdefd7cf470c37205dd89ad00fbe23fb9f0d Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Mon, 2 Dec 2019 10:51:34 -0800 Subject: [PATCH 087/255] minimap - do not render decorations outside the viewport Fixes #85817 --- src/vs/editor/browser/viewParts/minimap/minimap.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index 4703c176847..a90678b1fe0 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -850,6 +850,11 @@ export class Minimap extends ViewPart { charWidth: number): void { const y = (lineNumber - layout.startLineNumber) * lineHeight; + // Skip rendering the line if it's vertically outside our viewport + if (y + height < 0 || y > this._options.canvasOuterHeight) { + return; + } + // Cache line offset data so that it is only read once per line let lineIndexToXOffset = lineOffsetMap.get(lineNumber); const isFirstDecorationForLine = !lineIndexToXOffset; From 3d772fb16433dedf16637ebf0fe0740019864c97 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Mon, 2 Dec 2019 11:30:17 -0800 Subject: [PATCH 088/255] minimap - only create char renderer when needed --- src/vs/editor/browser/viewParts/minimap/minimap.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index a90678b1fe0..b2b451023ee 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -33,6 +33,7 @@ import { Color } from 'vs/base/common/color'; import { GestureEvent, EventType, Gesture } from 'vs/base/browser/touch'; import { MinimapCharRendererFactory } from 'vs/editor/browser/viewParts/minimap/minimapCharRendererFactory'; import { MinimapPosition } from 'vs/editor/common/model'; +import { once } from 'vs/base/common/functional'; function getMinimapLineHeight(renderMinimap: RenderMinimap, scale: number): number { if (renderMinimap === RenderMinimap.Text) { @@ -73,7 +74,7 @@ class MinimapOptions { public readonly fontScale: number; - public readonly charRenderer: MinimapCharRenderer; + public readonly charRenderer: () => MinimapCharRenderer; /** * container dom node left position (in CSS px) @@ -117,7 +118,7 @@ class MinimapOptions { const minimapOpts = options.get(EditorOption.minimap); this.showSlider = minimapOpts.showSlider; this.fontScale = Math.round(minimapOpts.scale * pixelRatio); - this.charRenderer = MinimapCharRendererFactory.create(this.fontScale, fontInfo.fontFamily); + this.charRenderer = once(() => MinimapCharRendererFactory.create(this.fontScale, fontInfo.fontFamily)); this.pixelRatio = pixelRatio; this.typicalHalfwidthCharacterWidth = fontInfo.typicalHalfwidthCharacterWidth; this.lineHeight = options.get(EditorOption.lineHeight); @@ -905,6 +906,7 @@ export class Minimap extends ViewPart { private renderLines(layout: MinimapLayout): RenderData { const renderMinimap = this._options.renderMinimap; + const charRenderer = this._options.charRenderer(); const startLineNumber = layout.startLineNumber; const endLineNumber = layout.endLineNumber; const minimapLineHeight = getMinimapLineHeight(renderMinimap, this._options.fontScale); @@ -946,7 +948,7 @@ export class Minimap extends ViewPart { useLighterFont, renderMinimap, this._tokensColorTracker, - this._options.charRenderer, + charRenderer, dy, tabSize, lineInfo.data[lineIndex]!, From 92a1abd450e1a7402dc84b4f9a95272dbbaa6dd8 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Mon, 2 Dec 2019 11:40:43 -0800 Subject: [PATCH 089/255] Add a real setting for `search.enableSearchEditorPreview`. --- .../workbench/contrib/search/browser/search.contribution.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index fea95600589..af5b9c2f727 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -813,6 +813,11 @@ configurationRegistry.registerConfiguration({ type: 'number', default: 300, markdownDescription: nls.localize('search.searchOnTypeDebouncePeriod', "When `#search.searchOnType#` is enabled, controls the timeout in milliseconds between a character being typed and the search starting. Has no effect when `search.searchOnType` is disabled.") + }, + 'search.enableSearchEditorPreview': { + type: 'boolean', + default: false, + description: nls.localize('search.enableSearchEditorPreview', "Experimental: When enabled, allows opening workspace search results in an editor.") } } }); From 104f90e0ebdc56f04a7f5e12fd28c317c6931efc Mon Sep 17 00:00:00 2001 From: Pine Wu Date: Mon, 2 Dec 2019 11:53:41 -0800 Subject: [PATCH 090/255] Fix #85715. Fix #85717 --- .../client/src/matchingTag.ts | 130 ++++++++++++------ 1 file changed, 85 insertions(+), 45 deletions(-) diff --git a/extensions/html-language-features/client/src/matchingTag.ts b/extensions/html-language-features/client/src/matchingTag.ts index 6816c2c5e32..d12726d35da 100644 --- a/extensions/html-language-features/client/src/matchingTag.ts +++ b/extensions/html-language-features/client/src/matchingTag.ts @@ -45,8 +45,8 @@ export function activateMatchingTagPosition( isEnabled = true; } - let prevCursorCount = 0; - let cursorCount = 0; + let prevCursors: readonly Selection[] = []; + let cursors: readonly Selection[] = []; let inMirrorMode = false; function onDidChangeTextEditorSelection(event: TextEditorSelectionChangeEvent) { @@ -54,67 +54,67 @@ export function activateMatchingTagPosition( return; } - prevCursorCount = cursorCount; - cursorCount = event.selections.length; + prevCursors = cursors; + cursors = event.selections; - if (cursorCount === 1) { - if (inMirrorMode && prevCursorCount === 2) { - return; + if (cursors.length === 1) { + if (inMirrorMode && prevCursors.length === 2) { + if (cursors[0].isEqual(prevCursors[0]) || cursors[0].isEqual(prevCursors[1])) { + return; + } } if (event.selections[0].isEmpty) { - matchingTagPositionProvider(event.textEditor.document, event.selections[0].active).then(position => { - if (position && window.activeTextEditor) { - inMirrorMode = true; - const newCursor = new Selection(position.line, position.character, position.line, position.character); - window.activeTextEditor.selections = [...window.activeTextEditor.selections, newCursor]; + matchingTagPositionProvider(event.textEditor.document, event.selections[0].active).then(matchingTagPosition => { + if (matchingTagPosition && window.activeTextEditor) { + const charBeforeAndAfterPositionsRoughtlyEqual = isCharBeforeAndAfterPositionsRoughtlyEqual( + event.textEditor.document, + event.selections[0].anchor, + new Position(matchingTagPosition.line, matchingTagPosition.character) + ); + + if (charBeforeAndAfterPositionsRoughtlyEqual) { + inMirrorMode = true; + const newCursor = new Selection( + matchingTagPosition.line, + matchingTagPosition.character, + matchingTagPosition.line, + matchingTagPosition.character + ); + window.activeTextEditor.selections = [...window.activeTextEditor.selections, newCursor]; + } } }); } } - if (cursorCount === 2 && inMirrorMode) { + if (cursors.length === 2 && inMirrorMode) { // Check two cases if (event.selections[0].isEmpty && event.selections[1].isEmpty) { - const charBeforePrimarySelection = getCharBefore(event.textEditor.document, event.selections[0].anchor); - const charAfterPrimarySelection = getCharAfter(event.textEditor.document, event.selections[0].anchor); - const charBeforeSecondarySelection = getCharBefore(event.textEditor.document, event.selections[1].anchor); - const charAfterSecondarySelection = getCharAfter(event.textEditor.document, event.selections[1].anchor); + const charBeforeAndAfterPositionsRoughtlyEqual = isCharBeforeAndAfterPositionsRoughtlyEqual( + event.textEditor.document, + event.selections[0].anchor, + event.selections[1].anchor + ); - // Exit mirror mode when cursor position no longer mirror - // Unless it's in the case of `<|>` - const charBeforeBothPositionRoughlyEqual = - charBeforePrimarySelection === charBeforeSecondarySelection || - (charBeforePrimarySelection === '/' && charBeforeSecondarySelection === '<') || - (charBeforeSecondarySelection === '/' && charBeforePrimarySelection === '<'); - const charAfterBothPositionRoughlyEqual = - charAfterPrimarySelection === charAfterSecondarySelection || - (charAfterPrimarySelection === ' ' && charAfterSecondarySelection === '>') || - (charAfterSecondarySelection === ' ' && charAfterPrimarySelection === '>'); - - if (!charBeforeBothPositionRoughlyEqual || !charAfterBothPositionRoughlyEqual) { + if (!charBeforeAndAfterPositionsRoughtlyEqual) { inMirrorMode = false; window.activeTextEditor!.selections = [window.activeTextEditor!.selections[0]]; return; } else { // Need to cleanup in the case of
if ( - charBeforePrimarySelection === ' ' && - charAfterPrimarySelection === '>' && - charBeforeSecondarySelection === ' ' && - charAfterSecondarySelection === '>' + shouldDoCleanupForHtmlAttributeInput( + event.textEditor.document, + event.selections[0].anchor, + event.selections[1].anchor + ) ) { - const primaryBeforeSecondary = - event.textEditor.document.offsetAt(event.selections[0].anchor) < - event.textEditor.document.offsetAt(event.selections[1].anchor); - - if (primaryBeforeSecondary) { - inMirrorMode = false; - const cleanupEdit = new WorkspaceEdit(); - const cleanupRange = new Range(event.selections[1].anchor.translate(0, -1), event.selections[1].anchor); - cleanupEdit.replace(event.textEditor.document.uri, cleanupRange, ''); - window.activeTextEditor!.selections = [window.activeTextEditor!.selections[0]]; - workspace.applyEdit(cleanupEdit); - } + inMirrorMode = false; + const cleanupEdit = new WorkspaceEdit(); + const cleanupRange = new Range(event.selections[1].anchor.translate(0, -1), event.selections[1].anchor); + cleanupEdit.replace(event.textEditor.document.uri, cleanupRange, ''); + window.activeTextEditor!.selections = [window.activeTextEditor!.selections[0]]; + workspace.applyEdit(cleanupEdit); } } } @@ -141,3 +141,43 @@ function getCharAfter(document: TextDocument, position: Position) { return document.getText(new Range(position, document.positionAt(offset + 1))); } + +// Check if chars before and after the two positions are equal +// For the chars before, `<` and `/` are consiered equal to handle the case of `<|>` +function isCharBeforeAndAfterPositionsRoughtlyEqual(document: TextDocument, firstPos: Position, secondPos: Position) { + const charBeforePrimarySelection = getCharBefore(document, firstPos); + const charAfterPrimarySelection = getCharAfter(document, firstPos); + const charBeforeSecondarySelection = getCharBefore(document, secondPos); + const charAfterSecondarySelection = getCharAfter(document, secondPos); + + // Exit mirror mode when cursor position no longer mirror + // Unless it's in the case of `<|>` + const charBeforeBothPositionRoughlyEqual = + charBeforePrimarySelection === charBeforeSecondarySelection || + (charBeforePrimarySelection === '/' && charBeforeSecondarySelection === '<') || + (charBeforeSecondarySelection === '/' && charBeforePrimarySelection === '<'); + const charAfterBothPositionRoughlyEqual = + charAfterPrimarySelection === charAfterSecondarySelection || + (charAfterPrimarySelection === ' ' && charAfterSecondarySelection === '>') || + (charAfterSecondarySelection === ' ' && charAfterPrimarySelection === '>'); + + return charBeforeBothPositionRoughlyEqual && charAfterBothPositionRoughlyEqual; +} + +function shouldDoCleanupForHtmlAttributeInput(document: TextDocument, firstPos: Position, secondPos: Position) { + // Need to cleanup in the case of
+ const charBeforePrimarySelection = getCharBefore(document, firstPos); + const charAfterPrimarySelection = getCharAfter(document, firstPos); + const charBeforeSecondarySelection = getCharBefore(document, secondPos); + const charAfterSecondarySelection = getCharAfter(document, secondPos); + + const primaryBeforeSecondary = document.offsetAt(firstPos) < document.offsetAt(secondPos); + + return ( + primaryBeforeSecondary && + charBeforePrimarySelection === ' ' && + charAfterPrimarySelection === '>' && + charBeforeSecondarySelection === ' ' && + charAfterSecondarySelection === '>' + ); +} From 5f1b81b4976cc6a31d46befb828c0fe64ce47f49 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 2 Dec 2019 21:43:59 +0100 Subject: [PATCH 091/255] [css/html/json] provide replace & insert proposals --- .../client/src/cssMain.ts | 29 ++- extensions/css-language-features/package.json | 11 + .../client/src/htmlMain.ts | 29 ++- .../html-language-features/package.json | 8 + .../client/src/jsonMain.ts | 36 ++- .../json-language-features/package.json | 217 +++++++++--------- .../npm/src/features/jsonContributions.ts | 19 +- 7 files changed, 227 insertions(+), 122 deletions(-) diff --git a/extensions/css-language-features/client/src/cssMain.ts b/extensions/css-language-features/client/src/cssMain.ts index 1bc18bf6346..936176e9103 100644 --- a/extensions/css-language-features/client/src/cssMain.ts +++ b/extensions/css-language-features/client/src/cssMain.ts @@ -5,8 +5,8 @@ import * as fs from 'fs'; import * as path from 'path'; -import { commands, CompletionItem, CompletionItemKind, ExtensionContext, languages, Position, Range, SnippetString, TextEdit, window, workspace } from 'vscode'; -import { Disposable, LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from 'vscode-languageclient'; +import { commands, CompletionItem, CompletionItemKind, ExtensionContext, languages, Position, Range, SnippetString, TextEdit, window, workspace, TextDocument, CompletionContext, CancellationToken, ProviderResult, CompletionList } from 'vscode'; +import { Disposable, LanguageClient, LanguageClientOptions, ServerOptions, TransportKind, ProvideCompletionItemsSignature } from 'vscode-languageclient'; import * as nls from 'vscode-nls'; import { getCustomDataPathsFromAllExtensions, getCustomDataPathsInAllWorkspaces } from './customData'; @@ -43,6 +43,31 @@ export function activate(context: ExtensionContext) { }, initializationOptions: { dataPaths + }, + middleware: { + // testing the replace / insert mode + provideCompletionItem(document: TextDocument, position: Position, context: CompletionContext, token: CancellationToken, next: ProvideCompletionItemsSignature): ProviderResult { + function updateRanges(item: CompletionItem) { + const range = item.range; + if (range && range.end.isAfter(position) && range.start.isBeforeOrEqual(position)) { + item.range2 = { inserting: new Range(range.start, position), replacing: range }; + item.range = undefined; + } + } + function updateProposals(r: CompletionItem[] | CompletionList | null | undefined): CompletionItem[] | CompletionList | null | undefined { + if (r) { + (Array.isArray(r) ? r : r.items).forEach(updateRanges); + } + return r; + } + const isThenable = (obj: ProviderResult): obj is Thenable => obj && (obj)['then']; + + const r = next(document, position, context, token); + if (isThenable(r)) { + return r.then(updateProposals); + } + return updateProposals(r); + } } }; diff --git a/extensions/css-language-features/package.json b/extensions/css-language-features/package.json index 702d9d755ca..ba1c6f95918 100644 --- a/extensions/css-language-features/package.json +++ b/extensions/css-language-features/package.json @@ -783,6 +783,17 @@ } } ], + "configurationDefaults": { + "[css]": { + "editor.suggest.insertMode": "replace" + }, + "[scss]": { + "editor.suggest.insertMode": "replace" + }, + "[less]": { + "editor.suggest.insertMode": "replace" + } + }, "jsonValidation": [ { "fileMatch": "*.css-data.json", diff --git a/extensions/html-language-features/client/src/htmlMain.ts b/extensions/html-language-features/client/src/htmlMain.ts index 194935b25fc..4d900214bd1 100644 --- a/extensions/html-language-features/client/src/htmlMain.ts +++ b/extensions/html-language-features/client/src/htmlMain.ts @@ -8,8 +8,8 @@ import * as fs from 'fs'; import * as nls from 'vscode-nls'; const localize = nls.loadMessageBundle(); -import { languages, ExtensionContext, IndentAction, Position, TextDocument, Range, CompletionItem, CompletionItemKind, SnippetString, workspace, Disposable, FormattingOptions, CancellationToken, ProviderResult, TextEdit } from 'vscode'; -import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind, RequestType, TextDocumentPositionParams, DocumentRangeFormattingParams, DocumentRangeFormattingRequest } from 'vscode-languageclient'; +import { languages, ExtensionContext, IndentAction, Position, TextDocument, Range, CompletionItem, CompletionItemKind, SnippetString, workspace, Disposable, FormattingOptions, CancellationToken, ProviderResult, TextEdit, CompletionContext, CompletionList } from 'vscode'; +import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind, RequestType, TextDocumentPositionParams, DocumentRangeFormattingParams, DocumentRangeFormattingRequest, ProvideCompletionItemsSignature } from 'vscode-languageclient'; import { EMPTY_ELEMENTS } from './htmlEmptyTagsShared'; import { activateTagClosing } from './tagClosing'; import TelemetryReporter from 'vscode-extension-telemetry'; @@ -72,6 +72,31 @@ export function activate(context: ExtensionContext) { dataPaths, provideFormatter: false, // tell the server to not provide formatting capability and ignore the `html.format.enable` setting. }, + middleware: { + // testing the replace / insert mode + provideCompletionItem(document: TextDocument, position: Position, context: CompletionContext, token: CancellationToken, next: ProvideCompletionItemsSignature): ProviderResult { + function updateRanges(item: CompletionItem) { + const range = item.range; + if (range && range.end.isAfter(position) && range.start.isBeforeOrEqual(position)) { + item.range2 = { inserting: new Range(range.start, position), replacing: range }; + item.range = undefined; + } + } + function updateProposals(r: CompletionItem[] | CompletionList | null | undefined): CompletionItem[] | CompletionList | null | undefined { + if (r) { + (Array.isArray(r) ? r : r.items).forEach(updateRanges); + } + return r; + } + const isThenable = (obj: ProviderResult): obj is Thenable => obj && (obj)['then']; + + const r = next(document, position, context, token); + if (isThenable(r)) { + return r.then(updateProposals); + } + return updateProposals(r); + } + } }; // Create the language client and start the client. diff --git a/extensions/html-language-features/package.json b/extensions/html-language-features/package.json index a641eb4ca46..5dedb79987b 100644 --- a/extensions/html-language-features/package.json +++ b/extensions/html-language-features/package.json @@ -180,6 +180,14 @@ } } }, + "configurationDefaults": { + "[html]": { + "editor.suggest.insertMode": "replace" + }, + "[handlebars]": { + "editor.suggest.insertMode": "replace" + } + }, "jsonValidation": [ { "fileMatch": "*.html-data.json", diff --git a/extensions/json-language-features/client/src/jsonMain.ts b/extensions/json-language-features/client/src/jsonMain.ts index d7c6684b3f2..23c42f795c9 100644 --- a/extensions/json-language-features/client/src/jsonMain.ts +++ b/extensions/json-language-features/client/src/jsonMain.ts @@ -10,8 +10,16 @@ import { xhr, XHRResponse, getErrorStatusDescription } from 'request-light'; const localize = nls.loadMessageBundle(); -import { workspace, window, languages, commands, ExtensionContext, extensions, Uri, LanguageConfiguration, Diagnostic, StatusBarAlignment, TextEditor, TextDocument, FormattingOptions, CancellationToken, ProviderResult, TextEdit, Range, Disposable } from 'vscode'; -import { LanguageClient, LanguageClientOptions, RequestType, ServerOptions, TransportKind, NotificationType, DidChangeConfigurationNotification, HandleDiagnosticsSignature, ResponseError, DocumentRangeFormattingParams, DocumentRangeFormattingRequest } from 'vscode-languageclient'; +import { + workspace, window, languages, commands, ExtensionContext, extensions, Uri, LanguageConfiguration, + Diagnostic, StatusBarAlignment, TextEditor, TextDocument, FormattingOptions, CancellationToken, + ProviderResult, TextEdit, Range, Position, Disposable, CompletionItem, CompletionList, CompletionContext +} from 'vscode'; +import { + LanguageClient, LanguageClientOptions, RequestType, ServerOptions, TransportKind, NotificationType, + DidChangeConfigurationNotification, HandleDiagnosticsSignature, ResponseError, DocumentRangeFormattingParams, + DocumentRangeFormattingRequest, ProvideCompletionItemsSignature +} from 'vscode-languageclient'; import TelemetryReporter from 'vscode-extension-telemetry'; import { hash } from './utils/hash'; @@ -132,6 +140,29 @@ export function activate(context: ExtensionContext) { } next(uri, diagnostics); + }, + // testing the replace / insert mode + provideCompletionItem(document: TextDocument, position: Position, context: CompletionContext, token: CancellationToken, next: ProvideCompletionItemsSignature): ProviderResult { + function updateRanges(item: CompletionItem) { + const range = item.range; + if (range && range.end.isAfter(position) && range.start.isBeforeOrEqual(position)) { + item.range2 = { inserting: new Range(range.start, position), replacing: range }; + item.range = undefined; + } + } + function updateProposals(r: CompletionItem[] | CompletionList | null | undefined): CompletionItem[] | CompletionList | null | undefined { + if (r) { + (Array.isArray(r) ? r : r.items).forEach(updateRanges); + } + return r; + } + const isThenable = (obj: ProviderResult): obj is Thenable => obj && (obj)['then']; + + const r = next(document, position, context, token); + if (isThenable(r)) { + return r.then(updateProposals); + } + return updateProposals(r); } } }; @@ -422,5 +453,4 @@ function readJSONFile(location: string) { console.log(`Problems reading ${location}: ${e}`); return {}; } - } diff --git a/extensions/json-language-features/package.json b/extensions/json-language-features/package.json index 833059f3c83..37417289ed2 100644 --- a/extensions/json-language-features/package.json +++ b/extensions/json-language-features/package.json @@ -1,113 +1,114 @@ { - "name": "json-language-features", - "displayName": "%displayName%", - "description": "%description%", - "version": "1.0.0", - "publisher": "vscode", - "license": "MIT", - "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", - "engines": { - "vscode": "0.10.x" - }, - "icon": "icons/json.png", - "activationEvents": [ - "onLanguage:json", - "onLanguage:jsonc" - ], - "main": "./client/out/jsonMain", - "enableProposedApi": true, - "scripts": { - "compile": "gulp compile-extension:json-language-features-client compile-extension:json-language-features-server", - "watch": "gulp watch-extension:json-language-features-client watch-extension:json-language-features-server", - "postinstall": "cd server && yarn install", - "install-client-next": "yarn add vscode-languageclient@next" - }, - "categories": [ - "Programming Languages" - ], - "contributes": { - "configuration": { - "id": "json", - "order": 20, - "type": "object", - "title": "JSON", - "properties": { - "json.schemas": { - "type": "array", - "scope": "resource", - "description": "%json.schemas.desc%", - "items": { - "type": "object", - "default": { - "fileMatch": [ - "/myfile" - ], - "url": "schemaURL" - }, - "properties": { - "url": { - "type": "string", - "default": "/user.schema.json", - "description": "%json.schemas.url.desc%" - }, - "fileMatch": { - "type": "array", - "items": { - "type": "string", - "default": "MyFile.json", - "description": "%json.schemas.fileMatch.item.desc%" - }, - "minItems": 1, - "description": "%json.schemas.fileMatch.desc%" - }, - "schema": { - "$ref": "http://json-schema.org/draft-07/schema#", - "description": "%json.schemas.schema.desc%" - } - } - } - }, - "json.format.enable": { - "type": "boolean", - "scope": "window", - "default": true, - "description": "%json.format.enable.desc%" - }, - "json.trace.server": { - "type": "string", - "scope": "window", - "enum": [ - "off", - "messages", - "verbose" - ], - "default": "off", - "description": "%json.tracing.desc%" - }, - "json.colorDecorators.enable": { - "type": "boolean", - "scope": "window", - "default": true, - "description": "%json.colorDecorators.enable.desc%", - "deprecationMessage": "%json.colorDecorators.enable.deprecationMessage%" - } - } + "name": "json-language-features", + "displayName": "%displayName%", + "description": "%description%", + "version": "1.0.0", + "publisher": "vscode", + "license": "MIT", + "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", + "engines": { + "vscode": "0.10.x" }, - "configurationDefaults": { - "[json]": { - "editor.quickSuggestions": { - "strings": true + "icon": "icons/json.png", + "activationEvents": [ + "onLanguage:json", + "onLanguage:jsonc" + ], + "main": "./client/out/jsonMain", + "enableProposedApi": true, + "scripts": { + "compile": "gulp compile-extension:json-language-features-client compile-extension:json-language-features-server", + "watch": "gulp watch-extension:json-language-features-client watch-extension:json-language-features-server", + "postinstall": "cd server && yarn install", + "install-client-next": "yarn add vscode-languageclient@next" + }, + "categories": [ + "Programming Languages" + ], + "contributes": { + "configuration": { + "id": "json", + "order": 20, + "type": "object", + "title": "JSON", + "properties": { + "json.schemas": { + "type": "array", + "scope": "resource", + "description": "%json.schemas.desc%", + "items": { + "type": "object", + "default": { + "fileMatch": [ + "/myfile" + ], + "url": "schemaURL" + }, + "properties": { + "url": { + "type": "string", + "default": "/user.schema.json", + "description": "%json.schemas.url.desc%" + }, + "fileMatch": { + "type": "array", + "items": { + "type": "string", + "default": "MyFile.json", + "description": "%json.schemas.fileMatch.item.desc%" + }, + "minItems": 1, + "description": "%json.schemas.fileMatch.desc%" + }, + "schema": { + "$ref": "http://json-schema.org/draft-07/schema#", + "description": "%json.schemas.schema.desc%" + } + } + } + }, + "json.format.enable": { + "type": "boolean", + "scope": "window", + "default": true, + "description": "%json.format.enable.desc%" + }, + "json.trace.server": { + "type": "string", + "scope": "window", + "enum": [ + "off", + "messages", + "verbose" + ], + "default": "off", + "description": "%json.tracing.desc%" + }, + "json.colorDecorators.enable": { + "type": "boolean", + "scope": "window", + "default": true, + "description": "%json.colorDecorators.enable.desc%", + "deprecationMessage": "%json.colorDecorators.enable.deprecationMessage%" + } + } + }, + "configurationDefaults": { + "[json]": { + "editor.quickSuggestions": { + "strings": true + }, + "editor.suggest.insertMode": "replace" + } } - } + }, + "dependencies": { + "request-light": "^0.2.5", + "vscode-extension-telemetry": "0.1.1", + "vscode-languageclient": "^6.0.0-next.3", + "vscode-nls": "^4.1.1" + }, + "devDependencies": { + "@types/node": "^12.11.7" } - }, - "dependencies": { - "request-light": "^0.2.5", - "vscode-extension-telemetry": "0.1.1", - "vscode-languageclient": "^6.0.0-next.3", - "vscode-nls": "^4.1.1" - }, - "devDependencies": { - "@types/node": "^12.11.7" - } } diff --git a/extensions/npm/src/features/jsonContributions.ts b/extensions/npm/src/features/jsonContributions.ts index e59b7866ec3..af91f401201 100644 --- a/extensions/npm/src/features/jsonContributions.ts +++ b/extensions/npm/src/features/jsonContributions.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Location, getLocation, createScanner, SyntaxKind, ScanError } from 'jsonc-parser'; +import { Location, getLocation, createScanner, SyntaxKind, ScanError, JSONScanner } from 'jsonc-parser'; import { basename } from 'path'; import { BowerJSONContribution } from './bowerJSONContribution'; import { PackageJSONContribution } from './packageJSONContribution'; @@ -111,7 +111,7 @@ export class JSONCompletionItemProvider implements CompletionItemProvider { add: (suggestion: CompletionItem) => { if (!proposed[suggestion.label]) { proposed[suggestion.label] = true; - suggestion.range = overwriteRange; + suggestion.range2 = { replacing: overwriteRange, inserting: new Range(overwriteRange.start, overwriteRange.start) }; items.push(suggestion); } }, @@ -123,8 +123,9 @@ export class JSONCompletionItemProvider implements CompletionItemProvider { let collectPromise: Thenable | null = null; if (location.isAtPropertyKey) { - const addValue = !location.previousNode || !location.previousNode.colonOffset; - const isLast = this.isLast(document, position); + const scanner = createScanner(document.getText(), true); + const addValue = !location.previousNode || !this.hasColonAfter(scanner, location.previousNode.offset + location.previousNode.length); + const isLast = this.isLast(scanner, document.offsetAt(position)); collectPromise = this.jsonContribution.collectPropertySuggestions(fileName, location, currentWord, addValue, isLast, collector); } else { if (location.path.length === 0) { @@ -153,15 +154,19 @@ export class JSONCompletionItemProvider implements CompletionItemProvider { return text.substring(i + 1, position.character); } - private isLast(document: TextDocument, position: Position): boolean { - const scanner = createScanner(document.getText(), true); - scanner.setPosition(document.offsetAt(position)); + private isLast(scanner: JSONScanner, offset: number): boolean { + scanner.setPosition(offset); let nextToken = scanner.scan(); if (nextToken === SyntaxKind.StringLiteral && scanner.getTokenError() === ScanError.UnexpectedEndOfString) { nextToken = scanner.scan(); } return nextToken === SyntaxKind.CloseBraceToken || nextToken === SyntaxKind.EOF; } + private hasColonAfter(scanner: JSONScanner, offset: number): boolean { + scanner.setPosition(offset); + return scanner.scan() === SyntaxKind.ColonToken; + } + } export const xhrDisabled = () => Promise.reject({ responseText: 'Use of online resources is disabled.' }); From c2c96f3f461b9a0165a902d27cc5fd3b6f725a16 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 2 Dec 2019 21:54:16 +0100 Subject: [PATCH 092/255] [json] update service --- extensions/json-language-features/server/package.json | 2 +- extensions/json-language-features/server/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/extensions/json-language-features/server/package.json b/extensions/json-language-features/server/package.json index 6dc132cc742..b4992717f5b 100644 --- a/extensions/json-language-features/server/package.json +++ b/extensions/json-language-features/server/package.json @@ -14,7 +14,7 @@ "dependencies": { "jsonc-parser": "^2.2.0", "request-light": "^0.2.5", - "vscode-json-languageservice": "^3.4.7", + "vscode-json-languageservice": "^3.4.8", "vscode-languageserver": "^6.0.0-next.3", "vscode-uri": "^2.1.1" }, diff --git a/extensions/json-language-features/server/yarn.lock b/extensions/json-language-features/server/yarn.lock index f0fde79ab0c..b5fcaf44bab 100644 --- a/extensions/json-language-features/server/yarn.lock +++ b/extensions/json-language-features/server/yarn.lock @@ -80,10 +80,10 @@ request-light@^0.2.5: https-proxy-agent "^2.2.3" vscode-nls "^4.1.1" -vscode-json-languageservice@^3.4.7: - version "3.4.7" - resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-3.4.7.tgz#8d85f3c1d46a1e58e9867d747552fb8c83d934fd" - integrity sha512-y3MN2+/yph3yoIHGmHu4ScYpm285L58XVvfGkd49xTQzLja4apxSbwzsYcP9QsqS0W7KuvoyiPhqksiudoMwjg== +vscode-json-languageservice@^3.4.8: + version "3.4.8" + resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-3.4.8.tgz#2fc14e0a2603ed704ab57e29f3bcce75106be2bd" + integrity sha512-8h3VjUU4r37LVleIV7YGYs6MlnwHs4qR002cJsL3Z/ebrKzgab1OkwzGocAAJY21rnLhVy4KjnIy7oN1XGPlqA== dependencies: jsonc-parser "^2.2.0" vscode-languageserver-textdocument "^1.0.0-next.4" From 6b69213fa4d49cbdc1ffab32c019364cd696f5c6 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 26 Nov 2019 14:46:56 -0800 Subject: [PATCH 093/255] Dismiss custom editor on rename if the editor no longer matches the new resource name --- .../customEditor/browser/customEditorInput.ts | 18 ++++---- .../customEditor/browser/customEditors.ts | 27 +++++------- .../customEditor/common/customEditor.ts | 41 ++++++++++++++++--- 3 files changed, 56 insertions(+), 30 deletions(-) diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index 78cba9eadda..edf216c14d9 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -159,12 +159,16 @@ export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { return this.fileDialogService.pickFileToSave({});//this.getSaveDialogOptions(defaultUri, availableFileSystems)); } - public handleMove(groupId: GroupIdentifier, uri: URI, options?: ITextEditorOptions): IEditorInput | undefined { - const webview = assertIsDefined(this.takeOwnershipOfWebview()); - return this.instantiationService.createInstance(CustomFileEditorInput, - uri, - this.viewType, - generateUuid(), - new Lazy(() => webview)); + public handleMove(_groupId: GroupIdentifier, uri: URI, options?: ITextEditorOptions): IEditorInput | undefined { + const editorInfo = this.customEditorService.getCustomEditor(this.viewType); + if (editorInfo?.matches(uri)) { + const webview = assertIsDefined(this.takeOwnershipOfWebview()); + return this.instantiationService.createInstance(CustomFileEditorInput, + uri, + this.viewType, + generateUuid(), + new Lazy(() => webview)); + } + return undefined; } } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index 9e87a796443..ea3e9dcdaf1 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { coalesce, distinct, find, mergeSort } from 'vs/base/common/arrays'; -import * as glob from 'vs/base/common/glob'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable } from 'vs/base/common/lifecycle'; import { basename, isEqual } from 'vs/base/common/resources'; @@ -32,14 +31,14 @@ import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/wo import { CustomFileEditorInput } from './customEditorInput'; const defaultEditorId = 'default'; -const defaultEditorInfo: CustomEditorInfo = { +const defaultEditorInfo = new CustomEditorInfo({ id: defaultEditorId, displayName: nls.localize('promptOpenWith.defaultEditor', "VS Code's standard text editor"), selector: [ { filenamePattern: '*' } ], priority: CustomEditorPriority.default, -}; +}); export class CustomEditorInfoStore { private readonly contributedEditors = new Map(); @@ -64,7 +63,7 @@ export class CustomEditorInfoStore { public getContributedEditors(resource: URI): readonly CustomEditorInfo[] { return Array.from(this.contributedEditors.values()).filter(customEditor => - customEditor.selector.some(selector => matches(selector, resource))); + customEditor.matches(resource)); } } @@ -97,12 +96,12 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ for (const extension of extensions) { for (const webviewEditorContribution of extension.value) { - this._editorInfoStore.add({ + this._editorInfoStore.add(new CustomEditorInfo({ id: webviewEditorContribution.viewType, displayName: webviewEditorContribution.displayName, selector: webviewEditorContribution.selector || [], priority: webviewEditorContribution.priority || CustomEditorPriority.default, - }); + })); } } this.updateContexts(); @@ -127,6 +126,10 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ return { resource, viewType: activeInput.viewType }; } + public getCustomEditor(viewType: string): CustomEditorInfo | undefined { + return this._editorInfoStore.get(viewType); + } + public getContributedCustomEditors(resource: URI): readonly CustomEditorInfo[] { return this._editorInfoStore.getContributedEditors(resource); } @@ -134,7 +137,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ public getUserConfiguredCustomEditors(resource: URI): readonly CustomEditorInfo[] { const rawAssociations = this.configurationService.getValue(customEditorsAssociationsKey) || []; return coalesce(rawAssociations - .filter(association => matches(association, resource)) + .filter(association => CustomEditorInfo.selectorMatches(association, resource)) .map(association => this._editorInfoStore.get(association.viewType))); } @@ -405,16 +408,6 @@ function priorityToRank(priority: CustomEditorPriority): number { } } -function matches(selector: CustomEditorSelector, resource: URI): boolean { - if (selector.filenamePattern) { - if (glob.match(selector.filenamePattern.toLowerCase(), basename(resource).toLowerCase())) { - return true; - } - } - - return false; -} - registerThemingParticipant((theme, collector) => { const shadow = theme.getColor(colorRegistry.scrollbarShadow); if (shadow) { diff --git a/src/vs/workbench/contrib/customEditor/common/customEditor.ts b/src/vs/workbench/contrib/customEditor/common/customEditor.ts index 9162ab0e3d2..135ffc819b7 100644 --- a/src/vs/workbench/contrib/customEditor/common/customEditor.ts +++ b/src/vs/workbench/contrib/customEditor/common/customEditor.ts @@ -4,11 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { Event } from 'vs/base/common/event'; +import * as glob from 'vs/base/common/glob'; +import { basename } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { EditorInput, IEditor, ISaveOptions, IRevertOptions } from 'vs/workbench/common/editor'; +import { EditorInput, IEditor, IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService'; @@ -29,6 +31,7 @@ export interface ICustomEditorService { readonly activeCustomEditor: ICustomEditor | undefined; + getCustomEditor(viewType: string): CustomEditorInfo | undefined; getContributedCustomEditors(resource: URI): readonly CustomEditorInfo[]; getUserConfiguredCustomEditors(resource: URI): readonly CustomEditorInfo[]; @@ -87,9 +90,35 @@ export interface CustomEditorSelector { readonly filenamePattern?: string; } -export interface CustomEditorInfo { - readonly id: string; - readonly displayName: string; - readonly priority: CustomEditorPriority; - readonly selector: readonly CustomEditorSelector[]; +export class CustomEditorInfo { + + public readonly id: string; + public readonly displayName: string; + public readonly priority: CustomEditorPriority; + public readonly selector: readonly CustomEditorSelector[]; + + constructor(descriptor: { + readonly id: string; + readonly displayName: string; + readonly priority: CustomEditorPriority; + readonly selector: readonly CustomEditorSelector[]; + }) { + this.id = descriptor.id; + this.displayName = descriptor.displayName; + this.priority = descriptor.priority; + this.selector = descriptor.selector; + } + + matches(resource: URI): boolean { + return this.selector.some(selector => CustomEditorInfo.selectorMatches(selector, resource)); + } + + static selectorMatches(selector: CustomEditorSelector, resource: URI): boolean { + if (selector.filenamePattern) { + if (glob.match(selector.filenamePattern.toLowerCase(), basename(resource).toLowerCase())) { + return true; + } + } + return false; + } } From e33be1b0afbe9c3cb7234c6edf4b7a3f868f7d7e Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 26 Nov 2019 15:30:01 -0800 Subject: [PATCH 094/255] Explicitly register for save and saveAs --- .../api/browser/mainThreadWebview.ts | 16 ++++++++++++++-- .../workbench/api/common/extHost.protocol.ts | 6 ++++++ src/vs/workbench/api/common/extHostWebview.ts | 19 +++++++++++++++---- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadWebview.ts b/src/vs/workbench/api/browser/mainThreadWebview.ts index 1bd635659f9..7af79adc7e2 100644 --- a/src/vs/workbench/api/browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/browser/mainThreadWebview.ts @@ -280,8 +280,6 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma this._proxy.$applyEdits(handle, editsToApply); } }); - model.onWillSave(e => { e.waitUntil(this._proxy.$onSave(handle)); }); - model.onWillSaveAs(e => { e.waitUntil(this._proxy.$onSaveAs(handle, e.resource.toJSON(), e.targetResource.toJSON())); }); webviewInput.onDisposeWebview(() => { this._customEditorService.models.disposeModel(model); @@ -315,6 +313,20 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma this._editorProviders.delete(viewType); } + public async $registerCapabilities(handle: extHostProtocol.WebviewPanelHandle, capabilities: readonly extHostProtocol.WebviewEditorCapabilities[]): Promise { + const webviewInput = this.getWebviewInput(handle); + const model = await this._customEditorService.models.loadOrCreate(webviewInput.getResource(), webviewInput.viewType); + + const capabilitiesSet = new Set(capabilities); + + if (capabilitiesSet.has(extHostProtocol.WebviewEditorCapabilities.Save)) { + model.onWillSave(e => { e.waitUntil(this._proxy.$onSave(handle)); }); + } + if (capabilitiesSet.has(extHostProtocol.WebviewEditorCapabilities.SaveAs)) { + model.onWillSaveAs(e => { e.waitUntil(this._proxy.$onSaveAs(handle, e.resource.toJSON(), e.targetResource.toJSON())); }); + } + } + public $onEdit(handle: extHostProtocol.WebviewPanelHandle, editData: any): void { const webview = this.getWebviewInput(handle); if (!(webview instanceof CustomFileEditorInput)) { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 4bf6846bd20..4df48aeb239 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -556,6 +556,11 @@ export interface WebviewExtensionDescription { readonly location: UriComponents; } +export enum WebviewEditorCapabilities { + Save, + SaveAs, +} + export interface MainThreadWebviewsShape extends IDisposable { $createWebviewPanel(extension: WebviewExtensionDescription, handle: WebviewPanelHandle, viewType: string, title: string, showOptions: WebviewPanelShowOptions, options: modes.IWebviewPanelOptions & modes.IWebviewOptions): void; $disposeWebview(handle: WebviewPanelHandle): void; @@ -573,6 +578,7 @@ export interface MainThreadWebviewsShape extends IDisposable { $registerEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions): void; $unregisterEditorProvider(viewType: string): void; + $registerCapabilities(handle: WebviewPanelHandle, capabilities: readonly WebviewEditorCapabilities[]): void; $onEdit(handle: WebviewPanelHandle, editJson: any): void; } diff --git a/src/vs/workbench/api/common/extHostWebview.ts b/src/vs/workbench/api/common/extHostWebview.ts index e59a0eebbe7..216fb35e53b 100644 --- a/src/vs/workbench/api/common/extHostWebview.ts +++ b/src/vs/workbench/api/common/extHostWebview.ts @@ -16,7 +16,7 @@ import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor'; import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview'; import * as vscode from 'vscode'; -import { ExtHostWebviewsShape, IMainContext, MainContext, MainThreadWebviewsShape, WebviewPanelHandle, WebviewPanelViewStateData } from './extHost.protocol'; +import { ExtHostWebviewsShape, IMainContext, MainContext, MainThreadWebviewsShape, WebviewPanelHandle, WebviewPanelViewStateData, WebviewEditorCapabilities } from './extHost.protocol'; import { Disposable as VSCodeDisposable } from './extHostTypes'; type IconPath = URI | { light: URI, dark: URI }; @@ -257,12 +257,11 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa } async _onSave(): Promise { - await assertIsDefined(this._capabilities).editingCapability?.save(); + await assertIsDefined(this._capabilities?.editingCapability)?.save(); } - async _onSaveAs(resource: vscode.Uri, targetResource: vscode.Uri): Promise { - await assertIsDefined(this._capabilities).editingCapability?.saveAs(resource, targetResource); + await assertIsDefined(this._capabilities?.editingCapability)?.saveAs(resource, targetResource); } private assertNotDisposed() { @@ -450,6 +449,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { this._webviewPanels.set(handle, revivedPanel); const capabilities = await provider.resolveWebviewEditor({ resource: URI.revive(input.resource) }, revivedPanel); revivedPanel._setCapabilities(capabilities); + this.registerCapabilites(handle, capabilities); // TODO: the first set of edits should likely be passed when resolving if (input.edits.length) { @@ -480,6 +480,17 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { private getWebviewPanel(handle: WebviewPanelHandle): ExtHostWebviewEditor | undefined { return this._webviewPanels.get(handle); } + + private registerCapabilites(handle: WebviewPanelHandle, capabilities: vscode.WebviewEditorCapabilities) { + const declaredCapabilites: WebviewEditorCapabilities[] = []; + if (capabilities.editingCapability?.save) { + declaredCapabilites.push(WebviewEditorCapabilities.Save); + } + if (capabilities.editingCapability?.saveAs) { + declaredCapabilites.push(WebviewEditorCapabilities.SaveAs); + } + this._proxy.$registerCapabilities(handle, declaredCapabilites); + } } function convertWebviewOptions( From 6781ef6bf5f59ee3dbe3255b91514fa3710eea24 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 26 Nov 2019 15:30:28 -0800 Subject: [PATCH 095/255] Populate custom editor save as dialog with current path --- .../customEditor/browser/customEditorInput.ts | 21 +++++-------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index edf216c14d9..4ec3e8f7dff 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -117,27 +117,13 @@ export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { return false; } - // Preserve view state by opening the editor first. In addition - // this allows the user to review the contents of the editor. - // let viewState: IEditorViewState | undefined = undefined; - // const editor = await this.editorService.openEditor(this, undefined, group); - // if (isTextEditor(editor)) { - // viewState = editor.getViewState(); - // } - let dialogPath = this._editorResource; - // if (this._editorResource.scheme === Schemas.untitled) { - // dialogPath = this.suggestFileName(resource); - // } - const target = await this.promptForPath(this._editorResource, dialogPath, options?.availableFileSystems); if (!target) { return false; // save cancelled } - await this._model.saveAs(this._editorResource, target, options); - - return true; + return await this._model.saveAs(this._editorResource, target, options); } public revert(options?: IRevertOptions): Promise { @@ -156,7 +142,10 @@ export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { // Help user to find a name for the file by opening it first await this.editorService.openEditor({ resource, options: { revealIfOpened: true, preserveFocus: true } }); - return this.fileDialogService.pickFileToSave({});//this.getSaveDialogOptions(defaultUri, availableFileSystems)); + return this.fileDialogService.pickFileToSave({ + availableFileSystems, + defaultUri + }); } public handleMove(_groupId: GroupIdentifier, uri: URI, options?: ITextEditorOptions): IEditorInput | undefined { From e3fb9ceca19857d4debc51b6d357609811c5995b Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 26 Nov 2019 15:32:50 -0800 Subject: [PATCH 096/255] Use single editable capability --- src/vs/workbench/api/browser/mainThreadWebview.ts | 5 +---- src/vs/workbench/api/common/extHost.protocol.ts | 3 +-- src/vs/workbench/api/common/extHostWebview.ts | 7 ++----- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadWebview.ts b/src/vs/workbench/api/browser/mainThreadWebview.ts index 7af79adc7e2..558cf599eaf 100644 --- a/src/vs/workbench/api/browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/browser/mainThreadWebview.ts @@ -318,11 +318,8 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma const model = await this._customEditorService.models.loadOrCreate(webviewInput.getResource(), webviewInput.viewType); const capabilitiesSet = new Set(capabilities); - - if (capabilitiesSet.has(extHostProtocol.WebviewEditorCapabilities.Save)) { + if (capabilitiesSet.has(extHostProtocol.WebviewEditorCapabilities.Editable)) { model.onWillSave(e => { e.waitUntil(this._proxy.$onSave(handle)); }); - } - if (capabilitiesSet.has(extHostProtocol.WebviewEditorCapabilities.SaveAs)) { model.onWillSaveAs(e => { e.waitUntil(this._proxy.$onSaveAs(handle, e.resource.toJSON(), e.targetResource.toJSON())); }); } } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 4df48aeb239..b7d4837a61e 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -557,8 +557,7 @@ export interface WebviewExtensionDescription { } export enum WebviewEditorCapabilities { - Save, - SaveAs, + Editable, } export interface MainThreadWebviewsShape extends IDisposable { diff --git a/src/vs/workbench/api/common/extHostWebview.ts b/src/vs/workbench/api/common/extHostWebview.ts index 216fb35e53b..ef8b03fcd93 100644 --- a/src/vs/workbench/api/common/extHostWebview.ts +++ b/src/vs/workbench/api/common/extHostWebview.ts @@ -483,11 +483,8 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { private registerCapabilites(handle: WebviewPanelHandle, capabilities: vscode.WebviewEditorCapabilities) { const declaredCapabilites: WebviewEditorCapabilities[] = []; - if (capabilities.editingCapability?.save) { - declaredCapabilites.push(WebviewEditorCapabilities.Save); - } - if (capabilities.editingCapability?.saveAs) { - declaredCapabilites.push(WebviewEditorCapabilities.SaveAs); + if (capabilities.editingCapability) { + declaredCapabilites.push(WebviewEditorCapabilities.Editable); } this._proxy.$registerCapabilities(handle, declaredCapabilites); } From d43ca55162683f26f8b817e12dcd90e555e154d1 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 2 Dec 2019 12:03:01 -0800 Subject: [PATCH 097/255] Move more functionality into registerCapabilities --- .../api/browser/mainThreadWebview.ts | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadWebview.ts b/src/vs/workbench/api/browser/mainThreadWebview.ts index 558cf599eaf..529d9e60f99 100644 --- a/src/vs/workbench/api/browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/browser/mainThreadWebview.ts @@ -271,16 +271,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma webviewInput.webview.options = options; webviewInput.webview.extension = extension; - const model = await this._customEditorService.models.loadOrCreate(webviewInput.getResource(), webviewInput.viewType); - - model.onUndo(edits => { this._proxy.$undoEdits(handle, edits.map(x => x.data)); }); - model.onApplyEdit(edits => { - const editsToApply = edits.filter(x => x.source !== webviewInput).map(x => x.data); - if (editsToApply.length) { - this._proxy.$applyEdits(handle, editsToApply); - } - }); - + const model = await this.getModel(webviewInput); webviewInput.onDisposeWebview(() => { this._customEditorService.models.disposeModel(model); }); @@ -315,15 +306,29 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma public async $registerCapabilities(handle: extHostProtocol.WebviewPanelHandle, capabilities: readonly extHostProtocol.WebviewEditorCapabilities[]): Promise { const webviewInput = this.getWebviewInput(handle); - const model = await this._customEditorService.models.loadOrCreate(webviewInput.getResource(), webviewInput.viewType); + const model = await this.getModel(webviewInput); const capabilitiesSet = new Set(capabilities); if (capabilitiesSet.has(extHostProtocol.WebviewEditorCapabilities.Editable)) { + model.onUndo(edits => { this._proxy.$undoEdits(handle, edits.map(x => x.data)); }); + + model.onApplyEdit(edits => { + const editsToApply = edits.filter(x => x.source !== webviewInput).map(x => x.data); + if (editsToApply.length) { + this._proxy.$applyEdits(handle, editsToApply); + } + }); + model.onWillSave(e => { e.waitUntil(this._proxy.$onSave(handle)); }); + model.onWillSaveAs(e => { e.waitUntil(this._proxy.$onSaveAs(handle, e.resource.toJSON(), e.targetResource.toJSON())); }); } } + private getModel(webviewInput: WebviewInput) { + return this._customEditorService.models.loadOrCreate(webviewInput.getResource(), webviewInput.viewType); + } + public $onEdit(handle: extHostProtocol.WebviewPanelHandle, editData: any): void { const webview = this.getWebviewInput(handle); if (!(webview instanceof CustomFileEditorInput)) { From cc378fa6ea74726001517596c9e759fc609917f7 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Mon, 2 Dec 2019 13:45:57 -0800 Subject: [PATCH 098/255] update distro and TPNs --- ThirdPartyNotices.txt | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ThirdPartyNotices.txt b/ThirdPartyNotices.txt index 440edf6844b..7a3dd5cf7b3 100644 --- a/ThirdPartyNotices.txt +++ b/ThirdPartyNotices.txt @@ -8,7 +8,7 @@ This project incorporates components from the projects listed below. The origina 1. atom/language-clojure version 0.22.7 (https://github.com/atom/language-clojure) 2. atom/language-coffee-script version 0.49.3 (https://github.com/atom/language-coffee-script) 3. atom/language-java version 0.31.3 (https://github.com/atom/language-java) -4. atom/language-sass version 0.61.4 (https://github.com/atom/language-sass) +4. atom/language-sass version 0.62.1 (https://github.com/atom/language-sass) 5. atom/language-shellscript version 0.26.0 (https://github.com/atom/language-shellscript) 6. atom/language-xml version 0.35.2 (https://github.com/atom/language-xml) 7. Colorsublime-Themes version 0.1.0 (https://github.com/Colorsublime/Colorsublime-Themes) @@ -27,13 +27,13 @@ This project incorporates components from the projects listed below. The origina 20. Ionic documentation version 1.2.4 (https://github.com/ionic-team/ionic-site) 21. ionide/ionide-fsgrammar (https://github.com/ionide/ionide-fsgrammar) 22. jeff-hykin/cpp-textmate-grammar version 1.12.11 (https://github.com/jeff-hykin/cpp-textmate-grammar) -23. jeff-hykin/cpp-textmate-grammar version 1.14.9 (https://github.com/jeff-hykin/cpp-textmate-grammar) +23. jeff-hykin/cpp-textmate-grammar version 1.14.13 (https://github.com/jeff-hykin/cpp-textmate-grammar) 24. js-beautify version 1.6.8 (https://github.com/beautify-web/js-beautify) 25. Jxck/assert version 1.0.0 (https://github.com/Jxck/assert) 26. language-docker (https://github.com/moby/moby) 27. language-go version 0.44.3 (https://github.com/atom/language-go) 28. language-less version 0.34.2 (https://github.com/atom/language-less) -29. language-php version 0.44.2 (https://github.com/atom/language-php) +29. language-php version 0.44.3 (https://github.com/atom/language-php) 30. language-rust version 0.4.12 (https://github.com/zargony/atom-language-rust) 31. MagicStack/MagicPython version 1.1.1 (https://github.com/MagicStack/MagicPython) 32. marked version 0.6.2 (https://github.com/markedjs/marked) @@ -589,7 +589,7 @@ END OF HTML 5.1 W3C Working Draft NOTICES AND INFORMATION ========================================= MIT License -Copyright (c) 2017 Yuki Ueda +Copyright (c) 2019 Yuki Ueda Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/package.json b/package.json index 5ee09edfd02..d1cc787db35 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.41.0", - "distro": "30dcc7436405dfa68898d0f3843551c589fc9008", + "distro": "2e24565c8e7c0009905c302d27a0ca7cac7efdc3", "author": { "name": "Microsoft Corporation" }, From 7ddf497849919fae58911920bceaa12bfcffafb3 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 2 Dec 2019 23:32:30 +0100 Subject: [PATCH 099/255] Add some inline documentation --- src/vs/vscode.proposed.d.ts | 98 ++++++++++++++++++++++++++++++++++--- 1 file changed, 90 insertions(+), 8 deletions(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index e9cc058941c..bced8ed8d58 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -83,14 +83,6 @@ declare module 'vscode' { build(): Uint32Array; } - /** - * A certain token (at index `i` is encoded using 5 uint32 integers): - * - at index `5*i` - `deltaLine`: token line number, relative to `SemanticColoringArea.line` - * - at index `5*i+1` - `deltaStart`: token start character offset inside the line (relative to 0 or the previous token if they are on the same line) - * - at index `5*i+2` - `length`: the length of the token - * - at index `5*i+3` - `tokenType`: will be looked up in `SemanticColoringLegend.tokenTypes` - * - at index `5*i+4` - `tokenModifiers`: each set bit will be looked up in `SemanticColoringLegend.tokenModifiers` - */ export class SemanticTokens { readonly resultId?: string; readonly data: Uint32Array; @@ -123,6 +115,96 @@ declare module 'vscode' { * semantic tokens. */ export interface SemanticTokensProvider { + /** + * A file can contain many tokens, perhaps even hundreds of thousands tokens. Therefore, to improve + * the memory consumption around describing semantic tokens, we have decided to avoid allocating objects + * and we have decided to represent tokens from a file as an array of integers. + * + * + * In short, each token takes 5 integers to represent, so a specific token i in the file consists of the following fields: + * - at index `5*i` - `deltaLine`: token line number, relative to the previous token + * - at index `5*i+1` - `deltaStart`: token start character, relative to the previous token (relative to 0 or the previous token's start if they are on the same line) + * - at index `5*i+2` - `length`: the length of the token. A token cannot be multiline. + * - at index `5*i+3` - `tokenType`: will be looked up in `SemanticTokensLegend.tokenTypes` + * - at index `5*i+4` - `tokenModifiers`: each set bit will be looked up in `SemanticTokensLegend.tokenModifiers` + * + * + * Here is an example for encoding a file with 3 tokens: + * ``` + * [ { line: 2, startChar: 5, length: 3, tokenType: "properties", tokenModifiers: ["private", "static"] }, + * { line: 2, startChar: 10, length: 4, tokenType: "types", tokenModifiers: [] }, + * { line: 5, startChar: 2, length: 7, tokenType: "classes", tokenModifiers: [] } ] + * ``` + * + * 1. First of all, a legend must be devised. This legend must be provided up-front and capture all possible token types. + * For this example, we will choose the following legend which is passed in when registering the provider: + * ``` + * { tokenTypes: ['', 'properties', 'types', 'classes'], + * tokenModifiers: ['', 'private', 'static'] } + * ``` + * + * 2. The first transformation is to encode `tokenType` and `tokenModifiers` as integers using the legend. Token types are looked + * up by index, so a `tokenType` value of `1` means `tokenTypes[1]`. Token modifiers are a set and they are looked up by a bitmap, + * so a `tokenModifier` value of `6` is first viewed as a bitmap `0b110`, so it will mean `[tokenModifiers[1], tokenModifiers[2]]` because + * bits 1 and 2 are set. Using this legend, the tokens now are: + * ``` + * [ { line: 2, startChar: 5, length: 3, tokenType: 1, tokenModifiers: 6 }, // 6 is 0b110 + * { line: 2, startChar: 10, length: 4, tokenType: 2, tokenModifiers: 0 }, + * { line: 5, startChar: 2, length: 7, tokenType: 3, tokenModifiers: 0 } ] + * ``` + * + * 3. Then, we will encode each token relative to the previous token in the file: + * ``` + * [ { deltaLine: 2, deltaStartChar: 5, length: 3, tokenType: 1, tokenModifiers: 6 }, + * // this token is on the same line as the first one, so the startChar is made relative + * { deltaLine: 0, deltaStartChar: 5, length: 4, tokenType: 2, tokenModifiers: 0 }, + * // this token is on a different line than the second one, so the startChar remains unchanged + * { deltaLine: 3, deltaStartChar: 2, length: 7, tokenType: 3, tokenModifiers: 0 } ] + * ``` + * + * 4. Finally, the integers are organized in a single array, which is a memory friendly representation: + * ``` + * // 1st token, 2nd token, 3rd token + * [ 2,5,3,1,6, 0,5,4,2,0, 3,2,7,3,0 ] + * ``` + * + * In principle, each call to `provideSemanticTokens` expects a complete representations of the semantic tokens. + * It is possible to simply return all the tokens at each call. + * + * But oftentimes, a small edit in the file will result in a small change to the above delta-based represented tokens. + * (In fact, that is why the above tokens are delta-encoded relative to their corresponding previous tokens). + * In such a case, if VS Code passes in the previous result id, it is possible for an advanced tokenization provider + * to return a delta to the integers array. + * + * To continue with the previous example, suppose a new line has been pressed at the beginning of the file, such that + * all the tokens are now one line lower, and that a new token has appeared since the last result on line 4. + * For example, the tokens might look like: + * ``` + * [ { line: 3, startChar: 5, length: 3, tokenType: "properties", tokenModifiers: ["private", "static"] }, + * { line: 3, startChar: 10, length: 4, tokenType: "types", tokenModifiers: [] }, + * { line: 4, startChar: 3, length: 5, tokenType: "properties", tokenModifiers: ["static"] }, + * { line: 6, startChar: 2, length: 7, tokenType: "classes", tokenModifiers: [] } ] + * ``` + * + * The integer encoding of all new tokens would be: + * ``` + * [ 3,5,3,1,6, 0,5,4,2,0, 1,3,5,1,2, 2,2,7,3,0 ] + * ``` + * + * A smart tokens provider can compute a diff from the previous result to the new result + * ``` + * [ 2,5,3,1,6, 0,5,4,2,0, 3,2,7,3,0 ] + * [ 3,5,3,1,6, 0,5,4,2,0, 1,3,5,1,2, 2,2,7,3,0 ] + * ``` + * and return as simple integer edits the diff: + * ``` + * { edits: [ + * { start: 0, deleteCount: 1, data: [3] } // replace integer at offset 0 with 3 + * { start: 10, deleteCount: 1, data: [1,3,5,1,2,2] } // replace integer at offset 10 with [1,3,5,1,2,2] + * ]} + * ``` + * All indices expressed in the returned diff represent indices in the old result array, so they all refer to the previous result state. + */ provideSemanticTokens(document: TextDocument, options: SemanticTokensRequestOptions, token: CancellationToken): ProviderResult; } From 3d4d0bf3240ae581f9c54929c07da4a950ab6170 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Mon, 2 Dec 2019 14:40:54 -0800 Subject: [PATCH 100/255] Keep track of disposeables --- extensions/search-result/src/extension.ts | 123 +++++++++++----------- 1 file changed, 63 insertions(+), 60 deletions(-) diff --git a/extensions/search-result/src/extension.ts b/extensions/search-result/src/extension.ts index ec90cd28fbd..55e082f35eb 100644 --- a/extensions/search-result/src/extension.ts +++ b/extensions/search-result/src/extension.ts @@ -12,76 +12,79 @@ const SEARCH_RESULT_SELECTOR = { language: 'search-result' }; let cachedLastParse: { version: number, parse: ParsedSearchResults } | undefined; -export function activate() { +export function activate(context: vscode.ExtensionContext) { + context.subscriptions.push( + vscode.commands.registerCommand('searchResult.rerunSearch', () => vscode.commands.executeCommand('search.action.rerunEditorSearch')), - vscode.commands.registerCommand('searchResult.rerunSearch', () => vscode.commands.executeCommand('search.action.rerunEditorSearch')); + vscode.languages.registerDocumentSymbolProvider(SEARCH_RESULT_SELECTOR, { + provideDocumentSymbols(document: vscode.TextDocument, token: vscode.CancellationToken): vscode.DocumentSymbol[] { + const results = parseSearchResults(document, token) + .filter(isFileLine) + .map(line => new vscode.DocumentSymbol( + line.path, + '', + vscode.SymbolKind.File, + line.allLocations.map(({ originSelectionRange }) => originSelectionRange!).reduce((p, c) => p.union(c), line.location.originSelectionRange!), + line.location.originSelectionRange!, + )); - vscode.languages.registerDocumentSymbolProvider(SEARCH_RESULT_SELECTOR, { - provideDocumentSymbols(document: vscode.TextDocument, token: vscode.CancellationToken): vscode.DocumentSymbol[] { - const results = parseSearchResults(document, token) - .filter(isFileLine) - .map(line => new vscode.DocumentSymbol( - line.path, - '', - vscode.SymbolKind.File, - line.allLocations.map(({ originSelectionRange }) => originSelectionRange!).reduce((p, c) => p.union(c), line.location.originSelectionRange!), - line.location.originSelectionRange!, - )); - - return results; - } - }); - - vscode.languages.registerCompletionItemProvider(SEARCH_RESULT_SELECTOR, { - provideCompletionItems(document: vscode.TextDocument, position: vscode.Position): vscode.CompletionItem[] { - - const line = document.lineAt(position.line); - if (position.line > 3) { return []; } - if (position.character === 0 || (position.character === 1 && line.text === '#')) { - const header = Array.from({ length: 4 }).map((_, i) => document.lineAt(i).text); - - return ['# Query:', '# Flags:', '# Including:', '# Excluding:'] - .filter(suggestion => header.every(line => line.indexOf(suggestion) === -1)) - .map(flag => ({ label: flag, insertText: (flag.slice(position.character)) + ' ' })); + return results; } + }), - if (line.text.indexOf('# Flags:') === -1) { return []; } + vscode.languages.registerCompletionItemProvider(SEARCH_RESULT_SELECTOR, { + provideCompletionItems(document: vscode.TextDocument, position: vscode.Position): vscode.CompletionItem[] { - return ['RegExp', 'CaseSensitive', 'IgnoreExcludeSettings', 'WordMatch'] - .filter(flag => line.text.indexOf(flag) === -1) - .map(flag => ({ label: flag, insertText: flag + ' ' })); - } - }, '#'); + const line = document.lineAt(position.line); + if (position.line > 3) { return []; } + if (position.character === 0 || (position.character === 1 && line.text === '#')) { + const header = Array.from({ length: 4 }).map((_, i) => document.lineAt(i).text); - vscode.languages.registerDefinitionProvider(SEARCH_RESULT_SELECTOR, { - provideDefinition(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): vscode.DefinitionLink[] { - const lineResult = parseSearchResults(document, token)[position.line]; - if (!lineResult) { return []; } - if (lineResult.type === 'file') { - // TODO: The multi-match peek UX isnt very smooth. - // return lineResult.allLocations.length > 1 ? lineResult.allLocations : [lineResult.location]; - return []; + return ['# Query:', '# Flags:', '# Including:', '# Excluding:'] + .filter(suggestion => header.every(line => line.indexOf(suggestion) === -1)) + .map(flag => ({ label: flag, insertText: (flag.slice(position.character)) + ' ' })); + } + + if (line.text.indexOf('# Flags:') === -1) { return []; } + + return ['RegExp', 'CaseSensitive', 'IgnoreExcludeSettings', 'WordMatch'] + .filter(flag => line.text.indexOf(flag) === -1) + .map(flag => ({ label: flag, insertText: flag + ' ' })); } + }, '#'), - return [lineResult.location]; - } - }); + vscode.languages.registerDefinitionProvider(SEARCH_RESULT_SELECTOR, { + provideDefinition(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): vscode.DefinitionLink[] { + const lineResult = parseSearchResults(document, token)[position.line]; + if (!lineResult) { return []; } + if (lineResult.type === 'file') { + // TODO: The multi-match peek UX isnt very smooth. + // return lineResult.allLocations.length > 1 ? lineResult.allLocations : [lineResult.location]; + return []; + } - vscode.languages.registerDocumentLinkProvider(SEARCH_RESULT_SELECTOR, { - async provideDocumentLinks(document: vscode.TextDocument, token: vscode.CancellationToken): Promise { - return parseSearchResults(document, token) - .filter(({ type }) => type === 'file') - .map(({ location }) => ({ range: location.originSelectionRange!, target: location.targetUri })); - } - }); + return [lineResult.location]; + } + }), - vscode.window.onDidChangeActiveTextEditor(e => { - if (e?.document.languageId === 'search-result') { - // Clear the parse whenever we open a new editor. - // Conservative because things like the URI might remain constant even if the contents change, and re-parsing even large files is relatively fast. - cachedLastParse = undefined; - } - }); + vscode.languages.registerDocumentLinkProvider(SEARCH_RESULT_SELECTOR, { + async provideDocumentLinks(document: vscode.TextDocument, token: vscode.CancellationToken): Promise { + return parseSearchResults(document, token) + .filter(({ type }) => type === 'file') + .map(({ location }) => ({ range: location.originSelectionRange!, target: location.targetUri })); + } + }), + + vscode.window.onDidChangeActiveTextEditor(e => { + if (e?.document.languageId === 'search-result') { + // Clear the parse whenever we open a new editor. + // Conservative because things like the URI might remain constant even if the contents change, and re-parsing even large files is relatively fast. + cachedLastParse = undefined; + } + }), + + { dispose() { cachedLastParse = undefined; } } + ); } From e63912b18c3c7a9564bb2d96cba723df5f4b6051 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 2 Dec 2019 14:41:27 -0800 Subject: [PATCH 101/255] Bump distro, microsoft/vscode-remote-release#1835 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d1cc787db35..7fd8c125aaf 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.41.0", - "distro": "2e24565c8e7c0009905c302d27a0ca7cac7efdc3", + "distro": "de758a726231fcf8e04422e65e29cd792f21c6c4", "author": { "name": "Microsoft Corporation" }, From 293cabc6a15616ea0093360dacc6fd8cc3a2cfe0 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 2 Dec 2019 14:58:23 -0800 Subject: [PATCH 102/255] Fix #85915 - don't search open editors from git --- src/vs/workbench/services/search/common/searchService.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vs/workbench/services/search/common/searchService.ts b/src/vs/workbench/services/search/common/searchService.ts index ddbe6ecb49e..65d2ff3bd44 100644 --- a/src/vs/workbench/services/search/common/searchService.ts +++ b/src/vs/workbench/services/search/common/searchService.ts @@ -409,6 +409,11 @@ export class SearchService extends Disposable implements ISearchService { return; } + // Exclude files from the git FileSystemProvider, e.g. to prevent open staged files from showing in search results + if (resource.scheme === 'gitfs') { + return; + } + if (!this.matches(resource, query)) { return; // respect user filters } From 4924a8b805ca2daa6806f51e253b262940add79e Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Mon, 2 Dec 2019 15:30:19 -0800 Subject: [PATCH 103/255] Fix #85629. Resize find widget properly when resizing happens. --- src/vs/editor/contrib/find/findWidget.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/vs/editor/contrib/find/findWidget.ts b/src/vs/editor/contrib/find/findWidget.ts index 5c9aee4a2ed..e4a21ad6652 100644 --- a/src/vs/editor/contrib/find/findWidget.ts +++ b/src/vs/editor/contrib/find/findWidget.ts @@ -706,10 +706,12 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas if (this._resized) { this._findInput.inputBox.layout(); - let findInputWidth = this._findInput.inputBox.width; + let findInputWidth = this._findInput.inputBox.element.clientWidth; if (findInputWidth > 0) { this._replaceInput.width = findInputWidth; } + } else if (this._isReplaceVisible) { + this._replaceInput.width = dom.getTotalWidth(this._findInput.domNode); } } @@ -1159,13 +1161,11 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas return; } - const inputBoxWidth = width - FIND_ALL_CONTROLS_WIDTH; const maxWidth = parseFloat(dom.getComputedStyle(this._domNode).maxWidth!) || 0; if (width > maxWidth) { return; } this._domNode.style.width = `${width}px`; - this._findInput.inputBox.width = inputBoxWidth; if (this._isReplaceVisible) { this._replaceInput.width = dom.getTotalWidth(this._findInput.domNode); } @@ -1197,10 +1197,8 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas */ } - const inputBoxWidth = width - FIND_ALL_CONTROLS_WIDTH; this._domNode.style.width = `${width}px`; - this._findInput.inputBox.width = inputBoxWidth; if (this._isReplaceVisible) { this._replaceInput.width = dom.getTotalWidth(this._findInput.domNode); } From b0fb9f17da7b34ab122d9308c497b900fbe3cada Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 2 Dec 2019 15:43:16 -0800 Subject: [PATCH 104/255] Note image preview's extension kind For #85819 --- extensions/image-preview/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/image-preview/package.json b/extensions/image-preview/package.json index 8be8486a54b..a0664f98e3d 100644 --- a/extensions/image-preview/package.json +++ b/extensions/image-preview/package.json @@ -2,7 +2,7 @@ "name": "image-preview", "displayName": "%displayName%", "description": "%description%", - "extensionKind": "ui", + "extensionKind": ["ui", "workspace"], "version": "1.0.0", "publisher": "vscode", "icon": "icon.png", From cd6c734313c0edf305de238e00a7cb99e913f2cc Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Mon, 2 Dec 2019 16:16:01 -0800 Subject: [PATCH 105/255] remove unused const -- monaco. --- src/vs/editor/contrib/find/findWidget.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/find/findWidget.ts b/src/vs/editor/contrib/find/findWidget.ts index e4a21ad6652..eef499b1a0e 100644 --- a/src/vs/editor/contrib/find/findWidget.ts +++ b/src/vs/editor/contrib/find/findWidget.ts @@ -63,7 +63,7 @@ const PART_WIDTH = 275; const FIND_INPUT_AREA_WIDTH = PART_WIDTH - 54; let MAX_MATCHES_COUNT_WIDTH = 69; -let FIND_ALL_CONTROLS_WIDTH = 17/** Find Input margin-left */ + (MAX_MATCHES_COUNT_WIDTH + 3 + 1) /** Match Results */ + 23 /** Button */ * 4 + 2/** sash */; +// let FIND_ALL_CONTROLS_WIDTH = 17/** Find Input margin-left */ + (MAX_MATCHES_COUNT_WIDTH + 3 + 1) /** Match Results */ + 23 /** Button */ * 4 + 2/** sash */; const FIND_INPUT_AREA_HEIGHT = 33; // The height of Find Widget when Replace Input is not visible. const ctrlEnterReplaceAllWarningPromptedKey = 'ctrlEnterReplaceAll.windows.donotask'; From c72ddff61022ec3ace576dc5964e8e3fdf4bbec2 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 2 Dec 2019 17:22:15 -0800 Subject: [PATCH 106/255] Add issue numbers to proposed API --- src/vs/vscode.proposed.d.ts | 130 +++++++++++++++++++----------------- 1 file changed, 70 insertions(+), 60 deletions(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index bced8ed8d58..e84f6df0d4a 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -253,7 +253,7 @@ declare module 'vscode' { //#endregion - //#region Rob: search provider + //#region TextSearchProvider: https://github.com/microsoft/vscode/issues/59921 /** * The parameters of a query for text search. @@ -397,32 +397,6 @@ declare module 'vscode' { limitHit?: boolean; } - /** - * The parameters of a query for file search. - */ - export interface FileSearchQuery { - /** - * The search pattern to match against file paths. - */ - pattern: string; - } - - /** - * Options that apply to file search. - */ - export interface FileSearchOptions extends SearchOptions { - /** - * The maximum number of results to be returned. - */ - maxResults?: number; - - /** - * A CancellationToken that represents the session for this search query. If the provider chooses to, this object can be used as the key for a cache, - * and searches with the same session object can search the same cache. When the token is cancelled, the session is complete and the cache can be cleared. - */ - session?: CancellationToken; - } - /** * A preview of the text result. */ @@ -482,6 +456,50 @@ declare module 'vscode' { export type TextSearchResult = TextSearchMatch | TextSearchContext; + /** + * A TextSearchProvider provides search results for text results inside files in the workspace. + */ + export interface TextSearchProvider { + /** + * Provide results that match the given text pattern. + * @param query The parameters for this query. + * @param options A set of options to consider while searching. + * @param progress A progress callback that must be invoked for all results. + * @param token A cancellation token. + */ + provideTextSearchResults(query: TextSearchQuery, options: TextSearchOptions, progress: Progress, token: CancellationToken): ProviderResult; + } + + //#endregion + + //#region FileSearchProvider: https://github.com/microsoft/vscode/issues/73524 + + /** + * The parameters of a query for file search. + */ + export interface FileSearchQuery { + /** + * The search pattern to match against file paths. + */ + pattern: string; + } + + /** + * Options that apply to file search. + */ + export interface FileSearchOptions extends SearchOptions { + /** + * The maximum number of results to be returned. + */ + maxResults?: number; + + /** + * A CancellationToken that represents the session for this search query. If the provider chooses to, this object can be used as the key for a cache, + * and searches with the same session object can search the same cache. When the token is cancelled, the session is complete and the cache can be cleared. + */ + session?: CancellationToken; + } + /** * A FileSearchProvider provides search results for files in the given folder that match a query string. It can be invoked by quickopen or other extensions. * @@ -501,20 +519,34 @@ declare module 'vscode' { provideFileSearchResults(query: FileSearchQuery, options: FileSearchOptions, token: CancellationToken): ProviderResult; } - /** - * A TextSearchProvider provides search results for text results inside files in the workspace. - */ - export interface TextSearchProvider { + export namespace workspace { /** - * Provide results that match the given text pattern. - * @param query The parameters for this query. - * @param options A set of options to consider while searching. - * @param progress A progress callback that must be invoked for all results. - * @param token A cancellation token. + * Register a search provider. + * + * Only one provider can be registered per scheme. + * + * @param scheme The provider will be invoked for workspace folders that have this file scheme. + * @param provider The provider. + * @return A [disposable](#Disposable) that unregisters this provider when being disposed. */ - provideTextSearchResults(query: TextSearchQuery, options: TextSearchOptions, progress: Progress, token: CancellationToken): ProviderResult; + export function registerFileSearchProvider(scheme: string, provider: FileSearchProvider): Disposable; + + /** + * Register a text search provider. + * + * Only one provider can be registered per scheme. + * + * @param scheme The provider will be invoked for workspace folders that have this file scheme. + * @param provider The provider. + * @return A [disposable](#Disposable) that unregisters this provider when being disposed. + */ + export function registerTextSearchProvider(scheme: string, provider: TextSearchProvider): Disposable; } + //#endregion + + //#region findTextInFiles: https://github.com/microsoft/vscode/issues/59924 + /** * Options that can be set on a findTextInFiles search. */ @@ -579,28 +611,6 @@ declare module 'vscode' { } export namespace workspace { - /** - * Register a search provider. - * - * Only one provider can be registered per scheme. - * - * @param scheme The provider will be invoked for workspace folders that have this file scheme. - * @param provider The provider. - * @return A [disposable](#Disposable) that unregisters this provider when being disposed. - */ - export function registerFileSearchProvider(scheme: string, provider: FileSearchProvider): Disposable; - - /** - * Register a text search provider. - * - * Only one provider can be registered per scheme. - * - * @param scheme The provider will be invoked for workspace folders that have this file scheme. - * @param provider The provider. - * @return A [disposable](#Disposable) that unregisters this provider when being disposed. - */ - export function registerTextSearchProvider(scheme: string, provider: TextSearchProvider): Disposable; - /** * Search text in files across all [workspace folders](#workspace.workspaceFolders) in the workspace. * @param query The query parameters for the search - the search string, whether it's case-sensitive, or a regex, or matches whole words. @@ -729,7 +739,7 @@ declare module 'vscode' { //#endregion - //#region Rob, Matt: logging + //#region LogLevel: https://github.com/microsoft/vscode/issues/85992 /** * The severity level of a log message From fe1231931141afca83e3c7ff29c97f333790868d Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 2 Dec 2019 17:34:22 -0800 Subject: [PATCH 107/255] Custom editor save as should replace existing editor --- .../contrib/customEditor/browser/customEditorInput.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index 4ec3e8f7dff..c9394ba4e92 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -17,6 +17,7 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { GroupIdentifier, IEditorInput, IRevertOptions, ISaveOptions, Verbosity } from 'vs/workbench/common/editor'; import { ICustomEditorModel, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; +import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { WebviewEditorOverlay } from 'vs/workbench/contrib/webview/browser/webview'; import { IWebviewWorkbenchService, LazilyResolvedWebviewEditorInput } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -123,7 +124,14 @@ export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { return false; // save cancelled } - return await this._model.saveAs(this._editorResource, target, options); + if (!await this._model.saveAs(this._editorResource, target, options)) { + return false; + } + + const replacement = this.handleMove(groupId, target) || this.instantiationService.createInstance(FileEditorInput, target, undefined, undefined); + + await this.editorService.replaceEditors([{ editor: this, replacement, options: { pinned: true } }], groupId); + return true; } public revert(options?: IRevertOptions): Promise { From 529351318e5874cc9720f45a5e24a3c6995b2c54 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 2 Dec 2019 17:53:32 -0800 Subject: [PATCH 108/255] Removing test custom editors --- build/lib/i18n.resources.json | 4 - .../browser/testCustomEditors.ts | 252 ------------------ src/vs/workbench/workbench.common.main.ts | 3 - 3 files changed, 259 deletions(-) delete mode 100644 src/vs/workbench/contrib/testCustomEditors/browser/testCustomEditors.ts diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index 6a3f89dc471..e9a4f279631 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -50,10 +50,6 @@ "name": "vs/workbench/contrib/comments", "project": "vscode-workbench" }, - { - "name": "vs/workbench/contrib/testCustomEditors", - "project": "vscode-workbench" - }, { "name": "vs/workbench/contrib/debug", "project": "vscode-workbench" diff --git a/src/vs/workbench/contrib/testCustomEditors/browser/testCustomEditors.ts b/src/vs/workbench/contrib/testCustomEditors/browser/testCustomEditors.ts deleted file mode 100644 index 661b8412cad..00000000000 --- a/src/vs/workbench/contrib/testCustomEditors/browser/testCustomEditors.ts +++ /dev/null @@ -1,252 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as nls from 'vs/nls'; -import { Action } from 'vs/base/common/actions'; -import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; -import { IEditorInputFactory, EditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, EditorModel, EditorOptions, GroupIdentifier, ISaveOptions, IRevertOptions } from 'vs/workbench/common/editor'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorModel } from 'vs/platform/editor/common/editor'; -import { Dimension, addDisposableListener, EventType } from 'vs/base/browser/dom'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; -import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IStorageService } from 'vs/platform/storage/common/storage'; -import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { URI } from 'vs/base/common/uri'; -import { isEqual } from 'vs/base/common/resources'; -import { generateUuid } from 'vs/base/common/uuid'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { editorBackground, editorForeground } from 'vs/platform/theme/common/colorRegistry'; -import { IWorkingCopy, IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; -import { env } from 'vs/base/common/process'; - -const CUSTOM_SCHEME = 'testCustomEditor'; -const ENABLE = !!env['VSCODE_DEV']; - -class TestCustomEditorsAction extends Action { - - static readonly ID = 'workbench.action.openCustomEditor'; - static readonly LABEL = nls.localize('openCustomEditor', "Test Open Custom Editor"); - - constructor( - id: string, - label: string, - @IEditorService private readonly editorService: IEditorService, - @IInstantiationService private readonly instantiationService: IInstantiationService - ) { - super(id, label); - } - - async run(): Promise { - const input = this.instantiationService.createInstance(TestCustomEditorInput, URI.parse(`${CUSTOM_SCHEME}:/${generateUuid()}`)); - await this.editorService.openEditor(input); - - return true; - } -} - -class TestCustomEditor extends BaseEditor { - - static ID = 'testCustomEditor'; - - private textArea: HTMLTextAreaElement | undefined = undefined; - - constructor( - @ITelemetryService telemetryService: ITelemetryService, - @IThemeService themeService: IThemeService, - @IStorageService storageService: IStorageService - ) { - super(TestCustomEditor.ID, telemetryService, themeService, storageService); - } - - updateStyles(): void { - super.updateStyles(); - - if (this.textArea) { - this.textArea.style.backgroundColor = this.getColor(editorBackground)!.toString(); - this.textArea.style.color = this.getColor(editorForeground)!.toString(); - } - } - - protected createEditor(parent: HTMLElement): void { - this.textArea = document.createElement('textarea'); - this.textArea.style.width = '100%'; - this.textArea.style.height = '100%'; - - parent.appendChild(this.textArea); - - addDisposableListener(this.textArea, EventType.CHANGE, e => this.onDidType()); - addDisposableListener(this.textArea, EventType.KEY_UP, e => this.onDidType()); - - this.updateStyles(); - } - - private onDidType(): void { - if (this._input instanceof TestCustomEditorInput) { - this._input.setValue(this.textArea!.value); - } - } - - async setInput(input: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { - await super.setInput(input, options, token); - - const model = await input.resolve(); - if (model instanceof TestCustomEditorModel) { - this.textArea!.value = model.value; - } - } - - clearInput() { - super.clearInput(); - - this.textArea!.value = ''; - } - - focus(): void { - this.textArea!.focus(); - } - - layout(dimension: Dimension): void { } -} - -class TestCustomEditorInput extends EditorInput implements IWorkingCopy { - private model: TestCustomEditorModel | undefined = undefined; - - private dirty = false; - - readonly capabilities = 0; - - constructor(public readonly resource: URI, @IWorkingCopyService workingCopyService: IWorkingCopyService) { - super(); - - this._register(workingCopyService.registerWorkingCopy(this)); - } - - getResource(): URI { - return this.resource; - } - - getTypeId(): string { - return TestCustomEditor.ID; - } - - getName(): string { - return this.resource.toString(); - } - - setValue(value: string) { - if (this.model) { - if (this.model.value === value) { - return; - } - - this.model.value = value; - } - - this.setDirty(value.length > 0); - } - - private setDirty(dirty: boolean) { - if (this.dirty !== dirty) { - this.dirty = dirty; - this._onDidChangeDirty.fire(); - } - } - - isReadonly(): boolean { - return false; - } - - isDirty(): boolean { - return this.dirty; - } - - async save(groupId: GroupIdentifier, options?: ISaveOptions): Promise { - this.setDirty(false); - - return true; - } - - async saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise { - this.setDirty(false); - - return true; - } - - async revert(options?: IRevertOptions): Promise { - this.setDirty(false); - - return true; - } - - async resolve(): Promise { - if (!this.model) { - this.model = new TestCustomEditorModel(this.resource); - } - - return this.model; - } - - matches(other: EditorInput) { - return other instanceof TestCustomEditorInput && isEqual(other.resource, this.resource); - } - - dispose(): void { - this.setDirty(false); - - if (this.model) { - this.model.dispose(); - this.model = undefined; - } - - super.dispose(); - } -} - -class TestCustomEditorModel extends EditorModel { - - public value: string = ''; - - constructor(public readonly resource: URI) { - super(); - } -} - -if (ENABLE) { - Registry.as(EditorExtensions.Editors).registerEditor( - EditorDescriptor.create( - TestCustomEditor, - TestCustomEditor.ID, - nls.localize('testCustomEditor', "Test Custom Editor") - ), - [ - new SyncDescriptor(TestCustomEditorInput), - ] - ); - - const registry = Registry.as(Extensions.WorkbenchActions); - - registry.registerWorkbenchAction(SyncActionDescriptor.create(TestCustomEditorsAction, TestCustomEditorsAction.ID, TestCustomEditorsAction.LABEL), 'Test Open Custom Editor'); - - class TestCustomEditorInputFactory implements IEditorInputFactory { - - serialize(editorInput: TestCustomEditorInput): string { - return JSON.stringify({ - resource: editorInput.resource.toString() - }); - } - - deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): TestCustomEditorInput { - return instantiationService.createInstance(TestCustomEditorInput, URI.parse(JSON.parse(serializedEditorInput).resource)); - } - } - - Registry.as(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(TestCustomEditor.ID, TestCustomEditorInputFactory); -} diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index d7de6c214e4..9f9f8a80d7c 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -260,7 +260,4 @@ import 'vs/workbench/contrib/userDataSync/browser/userDataSync.contribution'; // Code Actions import 'vs/workbench/contrib/codeActions/common/codeActions.contribution'; -// Test Custom Editors -import 'vs/workbench/contrib/testCustomEditors/browser/testCustomEditors'; - //#endregion From eae6eca8cfbb1923f244293d18b01df0591970f0 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Mon, 2 Dec 2019 19:39:28 -0800 Subject: [PATCH 109/255] [Search Editor] Add option for context lines --- extensions/search-result/package.json | 10 +++ extensions/search-result/package.nls.json | 3 +- extensions/search-result/src/extension.ts | 11 +-- .../syntaxes/searchResult.tmLanguage.json | 2 +- .../search/browser/search.contribution.ts | 7 +- .../contrib/search/browser/searchActions.ts | 34 +++++++++- .../contrib/search/browser/searchEditor.ts | 68 +++++++++++++++---- .../contrib/search/common/constants.ts | 1 + .../contrib/search/common/searchModel.ts | 19 +++++- 9 files changed, 133 insertions(+), 22 deletions(-) diff --git a/extensions/search-result/package.json b/extensions/search-result/package.json index bc7c6cf951c..0a2af5e0a9d 100644 --- a/extensions/search-result/package.json +++ b/extensions/search-result/package.json @@ -28,6 +28,15 @@ "light": "./src/media/refresh-light.svg", "dark": "./src/media/refresh-dark.svg" } + }, + { + "command": "searchResult.rerunSearchWithContext", + "title": "%searchResult.rerunSearchWithContext.title%", + "category": "Search Result", + "icon": { + "light": "./src/media/refresh-light.svg", + "dark": "./src/media/refresh-dark.svg" + } } ], "menus": { @@ -35,6 +44,7 @@ { "command": "searchResult.rerunSearch", "when": "editorLangId == search-result", + "alt": "searchResult.rerunSearchWithContext", "group": "navigation" } ] diff --git a/extensions/search-result/package.nls.json b/extensions/search-result/package.nls.json index 694f6b61d80..a4b0fb83845 100644 --- a/extensions/search-result/package.nls.json +++ b/extensions/search-result/package.nls.json @@ -1,5 +1,6 @@ { "displayName": "Search Result", "description": "Provides syntax highlighting and language features for tabbed search results.", - "searchResult.rerunSearch.title": "Search Again" + "searchResult.rerunSearch.title": "Search Again", + "searchResult.rerunSearchWithContext.title": "Search Again (Wth Context)" } diff --git a/extensions/search-result/src/extension.ts b/extensions/search-result/src/extension.ts index 55e082f35eb..c88a4feab63 100644 --- a/extensions/search-result/src/extension.ts +++ b/extensions/search-result/src/extension.ts @@ -7,14 +7,17 @@ import * as vscode from 'vscode'; import * as pathUtils from 'path'; const FILE_LINE_REGEX = /^(\S.*):$/; -const RESULT_LINE_REGEX = /^(\s+)(\d+):(\s+)(.*)$/; +const RESULT_LINE_REGEX = /^(\s+)(\d+)(?::| )(\s+)(.*)$/; const SEARCH_RESULT_SELECTOR = { language: 'search-result' }; +const DIRECTIVES = ['# Query:', '# Flags:', '# Including:', '# Excluding:', '# ContextLines:']; +const FLAGS = ['RegExp', 'CaseSensitive', 'IgnoreExcludeSettings', 'WordMatch']; let cachedLastParse: { version: number, parse: ParsedSearchResults } | undefined; export function activate(context: vscode.ExtensionContext) { context.subscriptions.push( vscode.commands.registerCommand('searchResult.rerunSearch', () => vscode.commands.executeCommand('search.action.rerunEditorSearch')), + vscode.commands.registerCommand('searchResult.rerunSearchWithContext', () => vscode.commands.executeCommand('search.action.rerunEditorSearchWithContext')), vscode.languages.registerDocumentSymbolProvider(SEARCH_RESULT_SELECTOR, { provideDocumentSymbols(document: vscode.TextDocument, token: vscode.CancellationToken): vscode.DocumentSymbol[] { @@ -38,16 +41,16 @@ export function activate(context: vscode.ExtensionContext) { const line = document.lineAt(position.line); if (position.line > 3) { return []; } if (position.character === 0 || (position.character === 1 && line.text === '#')) { - const header = Array.from({ length: 4 }).map((_, i) => document.lineAt(i).text); + const header = Array.from({ length: DIRECTIVES.length }).map((_, i) => document.lineAt(i).text); - return ['# Query:', '# Flags:', '# Including:', '# Excluding:'] + return DIRECTIVES .filter(suggestion => header.every(line => line.indexOf(suggestion) === -1)) .map(flag => ({ label: flag, insertText: (flag.slice(position.character)) + ' ' })); } if (line.text.indexOf('# Flags:') === -1) { return []; } - return ['RegExp', 'CaseSensitive', 'IgnoreExcludeSettings', 'WordMatch'] + return FLAGS .filter(flag => line.text.indexOf(flag) === -1) .map(flag => ({ label: flag, insertText: flag + ' ' })); } diff --git a/extensions/search-result/syntaxes/searchResult.tmLanguage.json b/extensions/search-result/syntaxes/searchResult.tmLanguage.json index 4de2a40ba40..d16ecb8c97c 100644 --- a/extensions/search-result/syntaxes/searchResult.tmLanguage.json +++ b/extensions/search-result/syntaxes/searchResult.tmLanguage.json @@ -3,7 +3,7 @@ "scopeName": "text.searchResult", "patterns": [ { - "match": "^# (Query|Flags|Including|Excluding): .*$", + "match": "^# (Query|Flags|Including|Excluding|ContextLines): .*$", "name": "comment" }, { diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index af5b9c2f727..76763901606 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -41,7 +41,7 @@ import { ExplorerFolderContext, ExplorerRootContext, FilesExplorerFocusCondition import { OpenAnythingHandler } from 'vs/workbench/contrib/search/browser/openAnythingHandler'; import { OpenSymbolHandler } from 'vs/workbench/contrib/search/browser/openSymbolHandler'; import { registerContributions as replaceContributions } from 'vs/workbench/contrib/search/browser/replaceContributions'; -import { clearHistoryCommand, ClearSearchResultsAction, CloseReplaceAction, CollapseDeepestExpandedLevelAction, copyAllCommand, copyMatchCommand, copyPathCommand, FocusNextInputAction, FocusNextSearchResultAction, FocusPreviousInputAction, FocusPreviousSearchResultAction, focusSearchListCommand, getSearchView, openSearchView, OpenSearchViewletAction, RefreshAction, RemoveAction, ReplaceAction, ReplaceAllAction, ReplaceAllInFolderAction, ReplaceInFilesAction, toggleCaseSensitiveCommand, toggleRegexCommand, toggleWholeWordCommand, FindInFilesCommand, ToggleSearchOnTypeAction, OpenResultsInEditorAction, RerunEditorSearchAction } from 'vs/workbench/contrib/search/browser/searchActions'; +import { clearHistoryCommand, ClearSearchResultsAction, CloseReplaceAction, CollapseDeepestExpandedLevelAction, copyAllCommand, copyMatchCommand, copyPathCommand, FocusNextInputAction, FocusNextSearchResultAction, FocusPreviousInputAction, FocusPreviousSearchResultAction, focusSearchListCommand, getSearchView, openSearchView, OpenSearchViewletAction, RefreshAction, RemoveAction, ReplaceAction, ReplaceAllAction, ReplaceAllInFolderAction, ReplaceInFilesAction, toggleCaseSensitiveCommand, toggleRegexCommand, toggleWholeWordCommand, FindInFilesCommand, ToggleSearchOnTypeAction, OpenResultsInEditorAction, RerunEditorSearchAction, RerunEditorSearchWithContextAction } from 'vs/workbench/contrib/search/browser/searchActions'; import { SearchPanel } from 'vs/workbench/contrib/search/browser/searchPanel'; import { SearchView, SearchViewPosition } from 'vs/workbench/contrib/search/browser/searchView'; import { SearchViewlet } from 'vs/workbench/contrib/search/browser/searchViewlet'; @@ -651,6 +651,11 @@ registry.registerWorkbenchAction( 'Search Editor: Search Again', category, ContextKeyExpr.and(EditorContextKeys.languageId.isEqualTo('search-result'))); +registry.registerWorkbenchAction( + SyncActionDescriptor.create(RerunEditorSearchWithContextAction, RerunEditorSearchWithContextAction.ID, RerunEditorSearchWithContextAction.LABEL), + 'Search Editor: Search Again (With Context)', category, + ContextKeyExpr.and(EditorContextKeys.languageId.isEqualTo('search-result'))); + // Register Quick Open Handler Registry.as(QuickOpenExtensions.Quickopen).registerDefaultQuickOpenHandler( diff --git a/src/vs/workbench/contrib/search/browser/searchActions.ts b/src/vs/workbench/contrib/search/browser/searchActions.ts index c228d3a1218..b8346873f74 100644 --- a/src/vs/workbench/contrib/search/browser/searchActions.ts +++ b/src/vs/workbench/contrib/search/browser/searchActions.ts @@ -32,6 +32,7 @@ import { ITreeNavigator } from 'vs/base/browser/ui/tree/tree'; import { createEditorFromSearchResult, refreshActiveEditorSearch } from 'vs/workbench/contrib/search/browser/searchEditor'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; export function isSearchViewFocused(viewletService: IViewletService, panelService: IPanelService): boolean { const searchView = getSearchView(viewletService, panelService); @@ -472,7 +473,38 @@ export class RerunEditorSearchAction extends Action { async run() { if (this.configurationService.getValue('search').enableSearchEditorPreview) { await this.progressService.withProgress({ location: ProgressLocation.Window }, - () => refreshActiveEditorSearch(this.editorService, this.instantiationService, this.contextService, this.labelService, this.configurationService)); + () => refreshActiveEditorSearch(undefined, this.editorService, this.instantiationService, this.contextService, this.labelService, this.configurationService)); + } + } +} + +export class RerunEditorSearchWithContextAction extends Action { + + static readonly ID: string = Constants.RerunEditorSearchWithContextCommandId; + static readonly LABEL = nls.localize('search.rerunEditorSearchContext', "Search Again (With Context)"); + + constructor(id: string, label: string, + @IInstantiationService private instantiationService: IInstantiationService, + @IEditorService private editorService: IEditorService, + @IConfigurationService private configurationService: IConfigurationService, + @IWorkspaceContextService private contextService: IWorkspaceContextService, + @ILabelService private labelService: ILabelService, + @IProgressService private progressService: IProgressService, + @IQuickInputService private quickPickService: IQuickInputService + ) { + super(id, label); + } + + async run() { + const lines = await this.quickPickService.input({ + prompt: nls.localize('lines', "Lines of Context"), + value: '2', + validateInput: async (value) => isNaN(parseInt(value)) ? nls.localize('mustBeInteger', "Must enter an integer") : undefined + }); + if (lines === undefined) { return; } + if (this.configurationService.getValue('search').enableSearchEditorPreview) { + await this.progressService.withProgress({ location: ProgressLocation.Window }, + () => refreshActiveEditorSearch(+lines, this.editorService, this.instantiationService, this.contextService, this.labelService, this.configurationService)); } } } diff --git a/src/vs/workbench/contrib/search/browser/searchEditor.ts b/src/vs/workbench/contrib/search/browser/searchEditor.ts index 4f515b013b1..46792e7b680 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/search/browser/searchEditor.ts @@ -65,6 +65,7 @@ const matchToSearchResultFormat = (match: Match): { line: string, ranges: Range[ }; type SearchResultSerialization = { text: string[], matchRanges: Range[] }; + function fileMatchToSearchResultFormat(fileMatch: FileMatch, labelFormatter: (x: URI) => string): SearchResultSerialization { const serializedMatches = flatten(fileMatch.matches() .sort(searchMatchComparer) @@ -76,17 +77,37 @@ function fileMatchToSearchResultFormat(fileMatch: FileMatch, labelFormatter: (x: const targetLineNumberToOffset: Record = {}; + const context: { line: string, lineNumber: number }[] = []; + fileMatch.context.forEach((line, lineNumber) => context.push({ line, lineNumber })); + context.sort((a, b) => a.lineNumber - b.lineNumber); + + let lastLine: number | undefined = undefined; + const seenLines = new Set(); serializedMatches.forEach(match => { if (!seenLines.has(match.line)) { + while (context.length && context[0].lineNumber < +match.lineNumber) { + const { line, lineNumber } = context.shift()!; + if (lastLine !== undefined && lineNumber !== lastLine + 1) { + text.push(''); + } + text.push(` ${lineNumber} ${line}`); + lastLine = lineNumber; + } + targetLineNumberToOffset[match.lineNumber] = text.length; seenLines.add(match.line); text.push(match.line); + lastLine = +match.lineNumber; } matchRanges.push(...match.ranges.map(translateRangeLines(targetLineNumberToOffset[match.lineNumber]))); }); + while (context.length) { + const { line, lineNumber } = context.shift()!; + text.push(` ${lineNumber} ${line}`); + } return { text, matchRanges }; } @@ -104,7 +125,7 @@ const flattenSearchResultSerializations = (serializations: SearchResultSerializa return { text, matchRanges }; }; -const contentPatternToSearchResultHeader = (pattern: ITextQuery | null, includes: string, excludes: string): string[] => { +const contentPatternToSearchResultHeader = (pattern: ITextQuery | null, includes: string, excludes: string, contextLines: number): string[] => { if (!pattern) { return []; } const removeNullFalseAndUndefined = (a: (T | null | false | undefined)[]) => a.filter(a => a !== false && a !== null && a !== undefined) as T[]; @@ -123,16 +144,32 @@ const contentPatternToSearchResultHeader = (pattern: ITextQuery | null, includes ]).join(' ')}`, includes ? `# Including: ${includes}` : undefined, excludes ? `# Excluding: ${excludes}` : undefined, + contextLines ? `# ContextLines: ${contextLines}` : undefined, '' ]); }; -const searchHeaderToContentPattern = (header: string[]): { pattern: string, flags: { regex: boolean, wholeWord: boolean, caseSensitive: boolean, ignoreExcludes: boolean }, includes: string, excludes: string } => { - const query = { + +type SearchHeader = { + pattern: string; + flags: { + regex: boolean; + wholeWord: boolean; + caseSensitive: boolean; + ignoreExcludes: boolean; + }; + includes: string; + excludes: string; + context: number | undefined; +}; + +const searchHeaderToContentPattern = (header: string[]): SearchHeader => { + const query: SearchHeader = { pattern: '', flags: { regex: false, caseSensitive: false, ignoreExcludes: false, wholeWord: false }, includes: '', - excludes: '' + excludes: '', + context: undefined }; const unescapeNewlines = (str: string) => str.replace(/\\\\/g, '\\').replace(/\\n/g, '\n'); @@ -145,6 +182,7 @@ const searchHeaderToContentPattern = (header: string[]): { pattern: string, flag case 'Query': query.pattern = unescapeNewlines(value); break; case 'Including': query.includes = value; break; case 'Excluding': query.excludes = value; break; + case 'ContextLines': query.context = +value; break; case 'Flags': { query.flags = { regex: value.indexOf('RegExp') !== -1, @@ -159,19 +197,20 @@ const searchHeaderToContentPattern = (header: string[]): { pattern: string, flag return query; }; -const serializeSearchResultForEditor = (searchResult: SearchResult, rawIncludePattern: string, rawExcludePattern: string, labelFormatter: (x: URI) => string): SearchResultSerialization => { - const header = contentPatternToSearchResultHeader(searchResult.query, rawIncludePattern, rawExcludePattern); +const serializeSearchResultForEditor = (searchResult: SearchResult, rawIncludePattern: string, rawExcludePattern: string, contextLines: number, labelFormatter: (x: URI) => string): SearchResultSerialization => { + const header = contentPatternToSearchResultHeader(searchResult.query, rawIncludePattern, rawExcludePattern, contextLines); const allResults = flattenSearchResultSerializations( - flatten(searchResult.folderMatches().sort(searchMatchComparer) - .map(folderMatch => folderMatch.matches().sort(searchMatchComparer) - .map(fileMatch => fileMatchToSearchResultFormat(fileMatch, labelFormatter))))); + flatten( + searchResult.folderMatches().sort(searchMatchComparer) + .map(folderMatch => folderMatch.matches().sort(searchMatchComparer) + .map(fileMatch => fileMatchToSearchResultFormat(fileMatch, labelFormatter))))); return { matchRanges: allResults.matchRanges.map(translateRangeLines(header.length)), text: header.concat(allResults.text) }; }; export const refreshActiveEditorSearch = - async (editorService: IEditorService, instantiationService: IInstantiationService, contextService: IWorkspaceContextService, labelService: ILabelService, configurationService: IConfigurationService) => { + async (contextLines: number | undefined, editorService: IEditorService, instantiationService: IInstantiationService, contextService: IWorkspaceContextService, labelService: ILabelService, configurationService: IConfigurationService) => { const model = editorService.activeTextEditorWidget?.getModel(); if (!model) { return; } @@ -190,6 +229,8 @@ export const refreshActiveEditorSearch = isWordMatch: contentPattern.flags.wholeWord }; + contextLines = contextLines ?? contentPattern.context ?? 0; + const options: ITextQueryBuilderOptions = { _reason: 'searchEditor', extraFileResources: instantiationService.invokeFunction(getOutOfWorkspaceEditorResources), @@ -202,6 +243,8 @@ export const refreshActiveEditorSearch = matchLines: 1, charsPerLine: 1000 }, + afterContext: contextLines, + beforeContext: contextLines, isSmartCase: configurationService.getValue('search').smartCase, expandPatterns: true }; @@ -220,7 +263,7 @@ export const refreshActiveEditorSearch = await searchModel.search(query); const labelFormatter = (uri: URI): string => labelService.getUriLabel(uri, { relative: true }); - const results = serializeSearchResultForEditor(searchModel.searchResult, '', '', labelFormatter); + const results = serializeSearchResultForEditor(searchModel.searchResult, contentPattern.includes, contentPattern.excludes, contextLines, labelFormatter); textModel.setValue(results.text.join(lineDelimiter)); textModel.deltaDecorations([], results.matchRanges.map(range => ({ range, options: { className: 'searchEditorFindMatch', stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges } }))); @@ -233,7 +276,7 @@ export const createEditorFromSearchResult = const labelFormatter = (uri: URI): string => labelService.getUriLabel(uri, { relative: true }); - const results = serializeSearchResultForEditor(searchResult, rawIncludePattern, rawExcludePattern, labelFormatter); + const results = serializeSearchResultForEditor(searchResult, rawIncludePattern, rawExcludePattern, 0, labelFormatter); let possible = { contents: results.text.join(lineDelimiter), @@ -255,7 +298,6 @@ export const createEditorFromSearchResult = model.deltaDecorations([], results.matchRanges.map(range => ({ range, options: { className: 'searchEditorFindMatch', stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges } }))); }; -// theming registerThemingParticipant((theme, collector) => { collector.addRule(`.monaco-editor .searchEditorFindMatch { background-color: ${theme.getColor(searchEditorFindMatch)}; }`); diff --git a/src/vs/workbench/contrib/search/common/constants.ts b/src/vs/workbench/contrib/search/common/constants.ts index 483190b6498..b7c72d5d5cd 100644 --- a/src/vs/workbench/contrib/search/common/constants.ts +++ b/src/vs/workbench/contrib/search/common/constants.ts @@ -17,6 +17,7 @@ export const CopyMatchCommandId = 'search.action.copyMatch'; export const CopyAllCommandId = 'search.action.copyAll'; export const OpenInEditorCommandId = 'search.action.openInEditor'; export const RerunEditorSearchCommandId = 'search.action.rerunEditorSearch'; +export const RerunEditorSearchWithContextCommandId = 'search.action.rerunEditorSearchWithContext'; export const ClearSearchHistoryCommandId = 'search.action.clearHistory'; export const FocusSearchListCommandID = 'search.action.focusSearchList'; export const ReplaceActionId = 'search.action.replace'; diff --git a/src/vs/workbench/contrib/search/common/searchModel.ts b/src/vs/workbench/contrib/search/common/searchModel.ts index 18ce0750f09..477b2de22e0 100644 --- a/src/vs/workbench/contrib/search/common/searchModel.ts +++ b/src/vs/workbench/contrib/search/common/searchModel.ts @@ -20,7 +20,7 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IProgress, IProgressStep } from 'vs/platform/progress/common/progress'; import { ReplacePattern } from 'vs/workbench/services/search/common/replace'; -import { IFileMatch, IPatternInfo, ISearchComplete, ISearchProgressItem, ISearchConfigurationProperties, ISearchService, ITextQuery, ITextSearchPreviewOptions, ITextSearchMatch, ITextSearchStats, resultIsMatch, ISearchRange, OneLineRange } from 'vs/workbench/services/search/common/search'; +import { IFileMatch, IPatternInfo, ISearchComplete, ISearchProgressItem, ISearchConfigurationProperties, ISearchService, ITextQuery, ITextSearchPreviewOptions, ITextSearchMatch, ITextSearchStats, resultIsMatch, ISearchRange, OneLineRange, ITextSearchContext, ITextSearchResult } from 'vs/workbench/services/search/common/search'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { overviewRulerFindMatchForeground, minimapFindMatch } from 'vs/platform/theme/common/colorRegistry'; import { themeColorFromId } from 'vs/platform/theme/common/themeService'; @@ -197,6 +197,11 @@ export class FileMatch extends Disposable implements IFileMatch { private _updateScheduler: RunOnceScheduler; private _modelDecorations: string[] = []; + private _context: Map = new Map(); + public get context(): Map { + return new Map(this._context); + } + constructor(private _query: IPatternInfo, private _previewOptions: ITextSearchPreviewOptions | undefined, private _maxResults: number | undefined, private _parent: FolderMatch, private rawMatch: IFileMatch, @IModelService private readonly modelService: IModelService, @IReplaceService private readonly replaceService: IReplaceService ) { @@ -221,6 +226,8 @@ export class FileMatch extends Disposable implements IFileMatch { textSearchResultToMatches(rawMatch, this) .forEach(m => this.add(m)); }); + + this.addContext(this.rawMatch.results); } } @@ -375,6 +382,14 @@ export class FileMatch extends Disposable implements IFileMatch { return getBaseLabel(this.resource); } + addContext(results: ITextSearchResult[] | undefined) { + if (!results) { return; } + + results + .filter((result => !resultIsMatch(result)) as ((a: any) => a is ITextSearchContext)) + .forEach(context => this._context.set(context.lineNumber, context.text)); + } + add(match: Match, trigger?: boolean) { this._matches.set(match.id(), match); if (trigger) { @@ -479,6 +494,8 @@ export class FolderMatch extends Disposable { .forEach(m => existingFileMatch.add(m)); }); updated.push(existingFileMatch); + + existingFileMatch.addContext(rawFileMatch.results); } else { const fileMatch = this.instantiationService.createInstance(FileMatch, this._query.contentPattern, this._query.previewOptions, this._query.maxResults, this, rawFileMatch); this.doAdd(fileMatch); From 97855786a014be2440751b038b373c3726e11fe8 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 2 Dec 2019 19:45:07 -0800 Subject: [PATCH 110/255] Fix absolute paths in markdown preview on windows Fixes #84728 We should use `.fsPath` for both parts of the uri in this case. --- .../markdown-language-features/src/markdownEngine.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/extensions/markdown-language-features/src/markdownEngine.ts b/extensions/markdown-language-features/src/markdownEngine.ts index d3b88f06c42..33cb220b449 100644 --- a/extensions/markdown-language-features/src/markdownEngine.ts +++ b/extensions/markdown-language-features/src/markdownEngine.ts @@ -242,8 +242,11 @@ export class MarkdownEngine { if (uri.path[0] === '/') { const root = vscode.workspace.getWorkspaceFolder(this.currentDocument!); if (root) { - uri = uri.with({ - path: path.join(root.uri.fsPath, uri.path), + const fileUri = vscode.Uri.file(path.join(root.uri.fsPath, uri.fsPath)); + uri = fileUri.with({ + scheme: uri.scheme, + fragment: uri.fragment, + query: uri.query, }); } } From 28c85551aefd561dba5a71315caf0b05ac08fc4e Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 3 Dec 2019 07:34:59 +0100 Subject: [PATCH 111/255] add test plan item template --- .github/ISSUE_TEMPLATE/test_plan_item.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/test_plan_item.md diff --git a/.github/ISSUE_TEMPLATE/test_plan_item.md b/.github/ISSUE_TEMPLATE/test_plan_item.md new file mode 100644 index 00000000000..aceb28efabb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/test_plan_item.md @@ -0,0 +1,18 @@ +--- +name: Test Plan Item +about: Create a test plan item describing how to test a feature +--- + +Refs: + +- [ ] Mac +- [ ] Linux +- [ ] Windows + +Complexity: + +Authors: + +--- + + From 86076f07711eb2969eddddfe4c9c8237fcf1e81f Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 3 Dec 2019 07:36:29 +0100 Subject: [PATCH 112/255] :lipstick: --- .github/ISSUE_TEMPLATE/test_plan_item.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/test_plan_item.md b/.github/ISSUE_TEMPLATE/test_plan_item.md index aceb28efabb..6aedc050556 100644 --- a/.github/ISSUE_TEMPLATE/test_plan_item.md +++ b/.github/ISSUE_TEMPLATE/test_plan_item.md @@ -3,7 +3,7 @@ name: Test Plan Item about: Create a test plan item describing how to test a feature --- -Refs: +Refs: - [ ] Mac - [ ] Linux From 2cb579c4c7a3510cf8a2346800494f997f5be1f9 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 3 Dec 2019 08:16:10 +0100 Subject: [PATCH 113/255] update author line --- .github/ISSUE_TEMPLATE/test_plan_item.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/test_plan_item.md b/.github/ISSUE_TEMPLATE/test_plan_item.md index 6aedc050556..816de53f677 100644 --- a/.github/ISSUE_TEMPLATE/test_plan_item.md +++ b/.github/ISSUE_TEMPLATE/test_plan_item.md @@ -11,7 +11,7 @@ Refs: Complexity: -Authors: +Authors: --- From 491a638b9812d524da219875c09fa2f6edb9d4fa Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 3 Dec 2019 08:41:45 +0100 Subject: [PATCH 114/255] include link to wiki --- .github/ISSUE_TEMPLATE/test_plan_item.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/test_plan_item.md b/.github/ISSUE_TEMPLATE/test_plan_item.md index 816de53f677..f386933832e 100644 --- a/.github/ISSUE_TEMPLATE/test_plan_item.md +++ b/.github/ISSUE_TEMPLATE/test_plan_item.md @@ -13,6 +13,8 @@ Complexity: Authors: + + --- From 56c6acd2fe1a2b7f53d4fd0cf0dfcc27837eb585 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 3 Dec 2019 09:44:10 +0100 Subject: [PATCH 115/255] update semantic test --- .../src/colorizerTestMain.ts | 43 ++++++++++++------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/extensions/vscode-colorize-tests/src/colorizerTestMain.ts b/extensions/vscode-colorize-tests/src/colorizerTestMain.ts index 76c1277f2ac..62aab90009d 100644 --- a/extensions/vscode-colorize-tests/src/colorizerTestMain.ts +++ b/extensions/vscode-colorize-tests/src/colorizerTestMain.ts @@ -8,31 +8,42 @@ import * as jsoncParser from 'jsonc-parser'; export function activate(context: vscode.ExtensionContext): any { - const tokenModifiers = ['static', 'abstract', 'deprecated']; - const tokenTypes = ['strings', 'types', 'structs', 'classes', 'functions', 'variables']; + const tokenModifiers = ['static', 'abstract', 'deprecated', 'declaration', 'documentation', 'member', 'async']; + const tokenTypes = ['types', 'structs', 'classes', 'interfaces', 'enums', 'parameterTypes', 'functions', 'variables']; + const legend = new vscode.SemanticTokensLegend(tokenTypes, tokenModifiers); const semanticHighlightProvider: vscode.SemanticTokensProvider = { provideSemanticTokens(document: vscode.TextDocument): vscode.ProviderResult { const builder = new vscode.SemanticTokensBuilder(); + function addToken(type: string, modifiers: string[], startLine: number, startCharacter: number, length: number) { + let tokenType = legend.tokenTypes.indexOf(type); + if (tokenType === -1) { + tokenType = 0; + } + + let tokenModifiers = 0; + for (let i = 0; i < modifiers.length; i++) { + const index = legend.tokenModifiers.indexOf(modifiers[i]); + if (index !== -1) { + tokenModifiers = tokenModifiers | 1 << index; + } + } + + builder.push(startLine, startCharacter, length, tokenType, tokenModifiers); + } + const visitor: jsoncParser.JSONVisitor = { onObjectProperty: (property: string, _offset: number, length: number, startLine: number, startCharacter: number) => { const [type, ...modifiers] = property.split('.'); - let tokenType = legend.tokenTypes.indexOf(type); - if (tokenType === -1) { - tokenType = 0; + addToken(type, modifiers, startLine, startCharacter, length); + }, + onLiteralValue: (value: any, _offset: number, length: number, startLine: number, startCharacter: number) => { + if (typeof value === 'string') { + const [type, ...modifiers] = value.split('.'); + addToken(type, modifiers, startLine, startCharacter, length); } - - let tokenModifiers = 0; - for (let i = 0; i < modifiers.length; i++) { - const index = legend.tokenModifiers.indexOf(modifiers[i]); - if (index !== -1) { - tokenModifiers = tokenModifiers | 1 << index; - } - } - - builder.push(startLine, startCharacter, length, tokenType, tokenModifiers); } }; jsoncParser.visit(document.getText(), visitor); @@ -42,6 +53,6 @@ export function activate(context: vscode.ExtensionContext): any { }; - context.subscriptions.push(vscode.languages.registerSemanticTokensProvider({ pattern: '**/color-test.json' }, semanticHighlightProvider, legend)); + context.subscriptions.push(vscode.languages.registerSemanticTokensProvider({ pattern: '**/*semantic-test.json' }, semanticHighlightProvider, legend)); } From 36ef46d4d05de205e065931c3db19e85a1f8b268 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 3 Dec 2019 10:39:36 +0100 Subject: [PATCH 116/255] remove tpi template --- .github/ISSUE_TEMPLATE/test_plan_item.md | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/test_plan_item.md diff --git a/.github/ISSUE_TEMPLATE/test_plan_item.md b/.github/ISSUE_TEMPLATE/test_plan_item.md deleted file mode 100644 index f386933832e..00000000000 --- a/.github/ISSUE_TEMPLATE/test_plan_item.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: Test Plan Item -about: Create a test plan item describing how to test a feature ---- - -Refs: - -- [ ] Mac -- [ ] Linux -- [ ] Windows - -Complexity: - -Authors: - - - ---- - - From 07241fc8edb0224432eb92cd44fed06a21902bf4 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 3 Dec 2019 10:43:21 +0100 Subject: [PATCH 117/255] remove space fixes #85869 --- build/win32/code.iss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/win32/code.iss b/build/win32/code.iss index 54bde39080a..5c995f26636 100644 --- a/build/win32/code.iss +++ b/build/win32/code.iss @@ -964,10 +964,10 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\Applications\{#ExeBas Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\Applications\{#ExeBasename}.exe\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\Applications\{#ExeBasename}.exe\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1""" -Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\*\shell\{#RegValueName}"; ValueType: expandsz; ValueName: ""; ValueData: "{cm:OpenWithCodeContextMenu, {#ShellNameShort}}"; Tasks: addcontextmenufiles; Flags: uninsdeletekey +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\*\shell\{#RegValueName}"; ValueType: expandsz; ValueName: ""; ValueData: "{cm:OpenWithCodeContextMenu,{#ShellNameShort}}"; Tasks: addcontextmenufiles; Flags: uninsdeletekey Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\*\shell\{#RegValueName}"; ValueType: expandsz; ValueName: "Icon"; ValueData: "{app}\{#ExeBasename}.exe"; Tasks: addcontextmenufiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\*\shell\{#RegValueName}\command"; ValueType: expandsz; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: addcontextmenufiles -Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\directory\shell\{#RegValueName}"; ValueType: expandsz; ValueName: ""; ValueData: "{cm:OpenWithCodeContextMenu, {#ShellNameShort}}"; Tasks: addcontextmenufolders; Flags: uninsdeletekey +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\directory\shell\{#RegValueName}"; ValueType: expandsz; ValueName: ""; ValueData: "{cm:OpenWithCodeContextMenu,{#ShellNameShort}}"; Tasks: addcontextmenufolders; Flags: uninsdeletekey Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\directory\shell\{#RegValueName}"; ValueType: expandsz; ValueName: "Icon"; ValueData: "{app}\{#ExeBasename}.exe"; Tasks: addcontextmenufolders Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\directory\shell\{#RegValueName}\command"; ValueType: expandsz; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%V"""; Tasks: addcontextmenufolders Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\directory\background\shell\{#RegValueName}"; ValueType: expandsz; ValueName: ""; ValueData: "Open w&ith {#ShellNameShort}"; Tasks: addcontextmenufolders; Flags: uninsdeletekey From 8b449d1a0874b52b9a2d088ed1be6a7fd8d0051b Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 3 Dec 2019 10:52:47 +0100 Subject: [PATCH 118/255] semantic token test: fix length, skip unknown tokens --- .../src/colorizerTestMain.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/extensions/vscode-colorize-tests/src/colorizerTestMain.ts b/extensions/vscode-colorize-tests/src/colorizerTestMain.ts index 62aab90009d..e9536994cf7 100644 --- a/extensions/vscode-colorize-tests/src/colorizerTestMain.ts +++ b/extensions/vscode-colorize-tests/src/colorizerTestMain.ts @@ -8,8 +8,8 @@ import * as jsoncParser from 'jsonc-parser'; export function activate(context: vscode.ExtensionContext): any { - const tokenModifiers = ['static', 'abstract', 'deprecated', 'declaration', 'documentation', 'member', 'async']; const tokenTypes = ['types', 'structs', 'classes', 'interfaces', 'enums', 'parameterTypes', 'functions', 'variables']; + const tokenModifiers = ['static', 'abstract', 'deprecated', 'declaration', 'documentation', 'member', 'async']; const legend = new vscode.SemanticTokensLegend(tokenTypes, tokenModifiers); @@ -17,10 +17,12 @@ export function activate(context: vscode.ExtensionContext): any { provideSemanticTokens(document: vscode.TextDocument): vscode.ProviderResult { const builder = new vscode.SemanticTokensBuilder(); - function addToken(type: string, modifiers: string[], startLine: number, startCharacter: number, length: number) { + function addToken(value: string, startLine: number, startCharacter: number, length: number) { + const [type, ...modifiers] = value.split('.'); + let tokenType = legend.tokenTypes.indexOf(type); if (tokenType === -1) { - tokenType = 0; + return; } let tokenModifiers = 0; @@ -31,18 +33,17 @@ export function activate(context: vscode.ExtensionContext): any { } } + builder.push(startLine, startCharacter, length, tokenType, tokenModifiers); } const visitor: jsoncParser.JSONVisitor = { - onObjectProperty: (property: string, _offset: number, length: number, startLine: number, startCharacter: number) => { - const [type, ...modifiers] = property.split('.'); - addToken(type, modifiers, startLine, startCharacter, length); + onObjectProperty: (property: string, _offset: number, _length: number, startLine: number, startCharacter: number) => { + addToken(property, startLine, startCharacter, property.length + 2); }, onLiteralValue: (value: any, _offset: number, length: number, startLine: number, startCharacter: number) => { if (typeof value === 'string') { - const [type, ...modifiers] = value.split('.'); - addToken(type, modifiers, startLine, startCharacter, length); + addToken(value, startLine, startCharacter, length); } } }; From 929080f70cf076ce16716b0495198205fac51d7d Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 3 Dec 2019 16:03:53 +0100 Subject: [PATCH 119/255] JSONC: Completion inserts one extra double quote. Fixes #86078 --- extensions/json-language-features/package.json | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/extensions/json-language-features/package.json b/extensions/json-language-features/package.json index 37417289ed2..69d898b9e7f 100644 --- a/extensions/json-language-features/package.json +++ b/extensions/json-language-features/package.json @@ -95,11 +95,17 @@ }, "configurationDefaults": { "[json]": { - "editor.quickSuggestions": { - "strings": true - }, - "editor.suggest.insertMode": "replace" - } + "editor.quickSuggestions": { + "strings": true + }, + "editor.suggest.insertMode": "replace" + }, + "[jsonc]": { + "editor.quickSuggestions": { + "strings": true + }, + "editor.suggest.insertMode": "replace" + } } }, "dependencies": { From 3c1508c227a0a4a7a10d56e9365b0f5f5d532749 Mon Sep 17 00:00:00 2001 From: isidor Date: Tue, 3 Dec 2019 16:35:47 +0100 Subject: [PATCH 120/255] fixes #83523 --- .../contrib/debug/browser/debugConfigurationManager.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index fcfb9fc251b..eecb9c57d6c 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -18,7 +18,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IFileService } from 'vs/platform/files/common/files'; -import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState, IWorkspaceFoldersChangeEvent } from 'vs/platform/workspace/common/workspace'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IDebugConfigurationProvider, ICompound, IDebugConfiguration, IConfig, IGlobalConfig, IConfigurationManager, ILaunch, IDebugAdapterDescriptorFactory, IDebugAdapter, IDebugSession, IAdapterDescriptor, CONTEXT_DEBUG_CONFIGURATION_TYPE, IDebugAdapterFactory } from 'vs/workbench/contrib/debug/common/debug'; @@ -284,7 +284,7 @@ export class ConfigurationManager implements IConfigurationManager { }); }); - this.toDispose.push(this.contextService.onDidChangeWorkspaceFolders(() => { + this.toDispose.push(Event.any(this.contextService.onDidChangeWorkspaceFolders, this.contextService.onDidChangeWorkbenchState)(() => { this.initLaunches(); const toSelect = this.selectedLaunch || (this.launches.length > 0 ? this.launches[0] : undefined); this.selectConfiguration(toSelect); From 9d47191895b0705f689a234c1a4e1e790303cb70 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Tue, 3 Dec 2019 10:37:36 -0800 Subject: [PATCH 121/255] Re #85313. Fix suggest widget position for extension editor. --- .../contrib/codeEditor/browser/simpleEditorOptions.ts | 4 ++-- .../browser/suggestEnabledInput/suggestEnabledInput.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts b/src/vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts index ac40c376e27..ff80bd1ec20 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts @@ -13,7 +13,7 @@ import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEdito import { TabCompletionController } from 'vs/workbench/contrib/snippets/browser/tabCompletion'; import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; -export function getSimpleEditorOptions(): IEditorOptions { +export function getSimpleEditorOptions(fixedOverflowWidgets: boolean = true): IEditorOptions { return { wordWrap: 'on', overviewRulerLanes: 0, @@ -30,7 +30,7 @@ export function getSimpleEditorOptions(): IEditorOptions { overviewRulerBorder: false, scrollBeyondLastLine: false, renderLineHighlight: 'none', - fixedOverflowWidgets: true, + fixedOverflowWidgets: fixedOverflowWidgets, acceptSuggestionOnEnter: 'smart', minimap: { enabled: false diff --git a/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts index b9731888148..eb0499385ca 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts @@ -126,7 +126,7 @@ export class SuggestEnabledInput extends Widget implements IThemable { this.placeholderText = append(this.stylingContainer, $('.suggest-input-placeholder', undefined, options.placeholderText || '')); const editorOptions: IEditorOptions = mixin( - getSimpleEditorOptions(), + getSimpleEditorOptions(false), getSuggestEnabledInputOptions(ariaLabel)); this.inputWidget = instantiationService.createInstance(CodeEditorWidget, this.stylingContainer, From 74ffa7942a107b9cfc45525e508ac601b79aacc1 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Tue, 3 Dec 2019 11:11:53 -0800 Subject: [PATCH 122/255] Fix layers overlap and cutting at edges. (#85724) * Fix layers overlap and cutting at edges. * limit part focus within css to windows and linux only --- src/vs/workbench/browser/media/part.css | 21 ++++++++++++++++++- .../activitybar/media/activitybarpart.css | 15 ------------- .../parts/sidebar/media/sidebarpart.css | 4 +--- .../parts/titlebar/media/titlebarpart.css | 16 +------------- 4 files changed, 22 insertions(+), 34 deletions(-) diff --git a/src/vs/workbench/browser/media/part.css b/src/vs/workbench/browser/media/part.css index 89dd7fe4c9b..46c88e56c1d 100644 --- a/src/vs/workbench/browser/media/part.css +++ b/src/vs/workbench/browser/media/part.css @@ -5,7 +5,26 @@ .monaco-workbench .part { box-sizing: border-box; - overflow: hidden; +} + +.monaco-workbench.windows.chromium .part, +.monaco-workbench.linux.chromium .part { + /* + * Explicitly put the part onto its own layer to help Chrome to + * render the content with LCD-anti-aliasing. By partioning the + * workbench into multiple layers, we can ensure that a bad + * behaving part is not making another part fallback to greyscale + * rendering. + * + * macOS: does not render LCD-anti-aliased. + */ + transform: translate3d(0px, 0px, 0px); + position: relative; +} + +.monaco-workbench.windows.chromium .part:focus-within, +.monaco-workbench.linux.chromium .part:focus-within { + z-index: 1000; } .monaco-workbench .part > .title { diff --git a/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css b/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css index 09bb59d2980..cbc3d14705b 100644 --- a/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css +++ b/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css @@ -7,21 +7,6 @@ width: 48px; } -.monaco-workbench.windows.chromium .part.activitybar, -.monaco-workbench.linux.chromium .part.activitybar { - /* - * Explicitly put the part onto its own layer to help Chrome to - * render the content with LCD-anti-aliasing. By partioning the - * workbench into multiple layers, we can ensure that a bad - * behaving part is not making another part fallback to greyscale - * rendering. - * - * macOS: does not render LCD-anti-aliased. - */ - transform: translate3d(0px, 0px, 0px); - overflow: visible; /* when a new layer is created, we need to set overflow visible to avoid clipping the menubar */ -} - .monaco-workbench .activitybar > .content { height: 100%; display: flex; diff --git a/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css b/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css index 1e2fad096a3..8307e81f13e 100644 --- a/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css +++ b/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css @@ -17,9 +17,7 @@ transform: translate3d(0px, 0px, 0px); } -.monaco-workbench .part.sidebar > .content { - overflow: hidden; -} + .monaco-workbench.nosidebar > .part.sidebar { display: none !important; diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index 54c67324b61..c44fc4bcac5 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -19,21 +19,7 @@ display: flex; } -.monaco-workbench.windows .part.titlebar, -.monaco-workbench.linux .part.titlebar { - /* - * Explicitly put the part onto its own layer to help Chrome to - * render the content with LCD-anti-aliasing. By partioning the - * workbench into multiple layers, we can ensure that a bad - * behaving part is not making another part fallback to greyscale - * rendering. - * - * macOS: does not render LCD-anti-aliased. - */ - transform: translate3d(0px, 0px, 0px); - position: relative; - z-index: 1000; /* move the entire titlebar above the workbench, except modals/dialogs */ -} + .monaco-workbench .part.titlebar > .titlebar-drag-region { top: 0; From a868166d9ece2af3be2579ce5c873117790db87b Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Tue, 3 Dec 2019 17:35:47 -0500 Subject: [PATCH 123/255] Removes codicons support in markdown images Will come back in a different form soon --- src/vs/base/browser/markdownRenderer.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/vs/base/browser/markdownRenderer.ts b/src/vs/base/browser/markdownRenderer.ts index 0a80b7cf934..58da2e0be68 100644 --- a/src/vs/base/browser/markdownRenderer.ts +++ b/src/vs/base/browser/markdownRenderer.ts @@ -15,7 +15,6 @@ import { cloneAndChange } from 'vs/base/common/objects'; import { escape } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; -import { renderCodicons } from 'vs/base/browser/ui/codiconLabel/codiconLabel'; export interface MarkdownRenderOptions extends FormattedTextRenderOptions { codeBlockRenderer?: (modeId: string, value: string) => Promise; @@ -73,10 +72,6 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende const renderer = new marked.Renderer(); renderer.image = (href: string, title: string, text: string) => { - if (href && href.indexOf('vscode-icon://codicon/') === 0) { - return renderCodicons(`$(${URI.parse(href).path.substr(1)})`); - } - let dimensions: string[] = []; let attributes: string[] = []; if (href) { From e1c3efa3be837f78680bf4208d0b7a6b7a84a456 Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Tue, 3 Dec 2019 15:49:33 -0800 Subject: [PATCH 124/255] Fix #86051 --- src/vs/workbench/contrib/remote/browser/remoteViewlet.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/remote/browser/remoteViewlet.css b/src/vs/workbench/contrib/remote/browser/remoteViewlet.css index 39b5cdec500..f0b017e457a 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteViewlet.css +++ b/src/vs/workbench/contrib/remote/browser/remoteViewlet.css @@ -35,5 +35,5 @@ } .monaco-workbench .part > .title > .title-actions .switch-remote > .monaco-select-box { - padding-left: 3px; + padding: 0 22px 0 6px; } From f0a557f56ecd7eb614357e777e124827d3cbb1dd Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Tue, 3 Dec 2019 15:54:28 -0800 Subject: [PATCH 125/255] Fix #86031, add rounded corners for remote input on mac --- src/vs/workbench/contrib/remote/browser/remoteViewlet.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vs/workbench/contrib/remote/browser/remoteViewlet.css b/src/vs/workbench/contrib/remote/browser/remoteViewlet.css index f0b017e457a..211bdc00027 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteViewlet.css +++ b/src/vs/workbench/contrib/remote/browser/remoteViewlet.css @@ -27,6 +27,11 @@ height: 20px; flex-shrink: 1; margin-top: 7px; + border-radius: 4px; +} + +.monaco-workbench.mac .part > .title > .title-actions .switch-remote { + border-radius: 4px; } .switch-remote > .monaco-select-box { From 5c3ecb4e101d1ca3252cb034edab6f9504efef2d Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Tue, 3 Dec 2019 15:55:48 -0800 Subject: [PATCH 126/255] Remove extra line for #86031 --- src/vs/workbench/contrib/remote/browser/remoteViewlet.css | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/contrib/remote/browser/remoteViewlet.css b/src/vs/workbench/contrib/remote/browser/remoteViewlet.css index 211bdc00027..c8f0d9f2964 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteViewlet.css +++ b/src/vs/workbench/contrib/remote/browser/remoteViewlet.css @@ -27,7 +27,6 @@ height: 20px; flex-shrink: 1; margin-top: 7px; - border-radius: 4px; } .monaco-workbench.mac .part > .title > .title-actions .switch-remote { From f4909e4f8e166d94086828b018b7c3eb54867038 Mon Sep 17 00:00:00 2001 From: Rachel Macfarlane Date: Tue, 3 Dec 2019 15:58:12 -0800 Subject: [PATCH 127/255] Add enum descriptions to scm.diffDecorations setting, fixes #86206 --- src/vs/workbench/contrib/scm/browser/scm.contribution.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index 0c40988389f..0021ac28e7d 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -81,6 +81,13 @@ Registry.as(ConfigurationExtensions.Configuration).regis 'scm.diffDecorations': { type: 'string', enum: ['all', 'gutter', 'overview', 'minimap', 'none'], + enumDescriptions: [ + localize('scm.diffDecorations.all', "Show the diff decorations in all available locations."), + localize('scm.diffDecorations.gutter', "Show the diff decorations only in the editor gutter."), + localize('scm.diffDecorations.overviewRuler', "Show the diff decorations only in the overview ruler."), + localize('scm.diffDecorations.minimap', "Show the diff decorations only in the minimap."), + localize('scm.diffDecorations.none', "Do not show the diff decorations.") + ], default: 'all', description: localize('diffDecorations', "Controls diff decorations in the editor.") }, From 2df315c3755dcd6de77385015e21e61134e84154 Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Tue, 3 Dec 2019 15:58:31 -0800 Subject: [PATCH 128/255] Fix #86136 --- .../contrib/debug/browser/debugCallStackContribution.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugCallStackContribution.ts b/src/vs/workbench/contrib/debug/browser/debugCallStackContribution.ts index ee78a18990e..a598a33d021 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCallStackContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCallStackContribution.ts @@ -239,5 +239,5 @@ const focusedStackFrameColor = registerColor('editor.focusedStackFrameHighlightB const debugIconBreakpointForeground = registerColor('debugIcon.breakpointForeground', { dark: '#E51400', light: '#E51400', hc: '#E51400' }, localize('debugIcon.breakpointForeground', 'Icon color for breakpoints.')); const debugIconBreakpointDisabledForeground = registerColor('debugIcon.breakpointDisabledForeground', { dark: '#848484', light: '#848484', hc: '#848484' }, localize('debugIcon.breakpointDisabledForeground', 'Icon color for disabled breakpoints.')); const debugIconBreakpointUnverifiedForeground = registerColor('debugIcon.breakpointUnverifiedForeground', { dark: '#848484', light: '#848484', hc: '#848484' }, localize('debugIcon.breakpointUnverifiedForeground', 'Icon color for unverified breakpoints.')); -const debugIconBreakpointStackframeForeground = registerColor('debugIcon.breakpointStackframeForeground', { dark: '#FFCC00', light: '#FFCC00', hc: '#FFCC00' }, localize('debugIcon.breakpointStackframeForeground', 'Icon color for breakpoints.')); -const debugIconBreakpointStackframeFocusedForeground = registerColor('debugIcon.breakpointStackframeFocusedForeground', { dark: '#89D185', light: '#89D185', hc: '#89D185' }, localize('debugIcon.breakpointStackframeFocusedForeground', 'Icon color for breakpoints.')); +const debugIconBreakpointStackframeForeground = registerColor('debugIcon.breakpointStackframeForeground', { dark: '#FFCC00', light: '#FFCC00', hc: '#FFCC00' }, localize('debugIcon.breakpointStackframeForeground', 'Icon color for breakpoint stack frames.')); +const debugIconBreakpointStackframeFocusedForeground = registerColor('debugIcon.breakpointStackframeFocusedForeground', { dark: '#89D185', light: '#89D185', hc: '#89D185' }, localize('debugIcon.breakpointStackframeFocusedForeground', 'Icon color for breakpoint stack frames that are focused.')); From 9794f2ba37fac4cb51478ac725f78f9a31e0ad30 Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Tue, 3 Dec 2019 16:07:25 -0800 Subject: [PATCH 129/255] Fix #86126 --- .../contrib/debug/browser/debugCallStackContribution.ts | 2 +- .../contrib/debug/browser/media/debug.contribution.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugCallStackContribution.ts b/src/vs/workbench/contrib/debug/browser/debugCallStackContribution.ts index a598a33d021..2959b8fd357 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCallStackContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCallStackContribution.ts @@ -186,7 +186,7 @@ registerThemingParticipant((theme, collector) => { .monaco-workbench .codicon-debug-breakpoint-function, .monaco-workbench .codicon-debug-breakpoint-data, .monaco-workbench .codicon-debug-breakpoint-unsupported, - .monaco-workbench .codicon-debug-hint:not(*[class*='codicon-debug-breakpoint']) , + .monaco-workbench .codicon-debug-hint:not([class*='codicon-debug-breakpoint']), .monaco-workbench .codicon-debug-breakpoint-stackframe-dot, .monaco-workbench .codicon-debug-breakpoint.codicon-debug-breakpoint-stackframe-focused::after { color: ${debugIconBreakpointColor} !important; diff --git a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css index 47fdbe45c60..215fb619865 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css +++ b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css @@ -7,7 +7,7 @@ cursor: pointer; } -.codicon-debug-hint:not(*[class*='codicon-debug-breakpoint']) { +.codicon-debug-hint:not([class*='codicon-debug-breakpoint']) { opacity: .4 !important; } From 27a00daa4031d394d73cdedc980f0a47c8be212a Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Tue, 3 Dec 2019 16:13:29 -0800 Subject: [PATCH 130/255] Increse search rendering delay. Fixes #86179 --- src/vs/workbench/contrib/search/common/searchModel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/search/common/searchModel.ts b/src/vs/workbench/contrib/search/common/searchModel.ts index 477b2de22e0..641a7557a37 100644 --- a/src/vs/workbench/contrib/search/common/searchModel.ts +++ b/src/vs/workbench/contrib/search/common/searchModel.ts @@ -999,7 +999,7 @@ export class SearchModel extends Disposable { this._replacePattern = new ReplacePattern(this.replaceString, this._searchQuery.contentPattern); // In search on type case, delay the streaming of results just a bit, so that we don't flash the only "local results" fast path - this._startStreamDelay = new Promise(resolve => setTimeout(resolve, this.searchConfig.searchOnType ? 100 : 0)); + this._startStreamDelay = new Promise(resolve => setTimeout(resolve, this.searchConfig.searchOnType ? 150 : 0)); const tokenSource = this.currentCancelTokenSource = new CancellationTokenSource(); const currentRequest = this.searchService.textSearch(this._searchQuery, this.currentCancelTokenSource.token, p => { From 43e3581aa635406ada2d8138d7ee242ddabefc1c Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 3 Dec 2019 16:44:20 -0800 Subject: [PATCH 131/255] Make sure custom editor prompt is not instantly dismissed by focus change Fixes #81765 --- src/vs/workbench/contrib/customEditor/browser/customEditors.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index ea3e9dcdaf1..49e2d2fcb4d 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -335,6 +335,9 @@ export class CustomEditorContribution implements IWorkbenchContribution { return { override: (async () => { const standardEditor = await this.editorService.openEditor(editor, { ...options, ignoreOverrides: true }, group); + // Give a moment to make sure the editor is showing. + // Otherwise the focus shift can cause the prompt to be dismissed right away. + await new Promise(resolve => setTimeout(resolve, 20)); const selectedEditor = await this.customEditorService.promptOpenWith(resource, options, group); if (selectedEditor && selectedEditor.input) { await group.replaceEditors([{ From 2111655b67a3cd7a85320003e862f0e4b1d547e0 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 4 Dec 2019 09:32:50 +0100 Subject: [PATCH 132/255] Disable view hiding for the remote explorer (#86110) Fixes https://github.com/microsoft/vscode/issues/86039 --- .../browser/parts/views/viewsViewlet.ts | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/browser/parts/views/viewsViewlet.ts b/src/vs/workbench/browser/parts/views/viewsViewlet.ts index 1399797b257..b178ef2f317 100644 --- a/src/vs/workbench/browser/parts/views/viewsViewlet.ts +++ b/src/vs/workbench/browser/parts/views/viewsViewlet.ts @@ -101,8 +101,16 @@ export abstract class ViewContainerViewlet extends PaneViewlet implements IViews this.focus(); } - getContextMenuActions(): IAction[] { + getContextMenuActions(viewDescriptor?: IViewDescriptor): IAction[] { const result: IAction[] = []; + if (viewDescriptor) { + result.push({ + id: `${viewDescriptor.id}.removeView`, + label: localize('hideView', "Hide"), + enabled: viewDescriptor.canToggleVisibility, + run: () => this.toggleViewVisibility(viewDescriptor.id) + }); + } const viewToggleActions = this.viewsModel.viewDescriptors.map(viewDescriptor => ({ id: `${viewDescriptor.id}.toggleVisibility`, label: viewDescriptor.name, @@ -111,13 +119,17 @@ export abstract class ViewContainerViewlet extends PaneViewlet implements IViews run: () => this.toggleViewVisibility(viewDescriptor.id) })); - result.push(...viewToggleActions); - const parentActions = this.getViewletContextMenuActions(); - if (viewToggleActions.length && parentActions.length) { + if (result.length && viewToggleActions.length) { result.push(new Separator()); } + result.push(...viewToggleActions); + const parentActions = this.getViewletContextMenuActions(); + if (result.length && parentActions.length) { + result.push(new Separator()); + } result.push(...parentActions); + return result; } @@ -249,17 +261,7 @@ export abstract class ViewContainerViewlet extends PaneViewlet implements IViews event.stopPropagation(); event.preventDefault(); - const actions: IAction[] = []; - actions.push({ - id: `${viewDescriptor.id}.removeView`, - label: localize('hideView', "Hide"), - enabled: viewDescriptor.canToggleVisibility, - run: () => this.toggleViewVisibility(viewDescriptor.id) - }); - const otherActions = this.getContextMenuActions(); - if (otherActions.length) { - actions.push(...[new Separator(), ...otherActions]); - } + const actions: IAction[] = this.getContextMenuActions(viewDescriptor); let anchor: { x: number, y: number } = { x: event.posx, y: event.posy }; this.contextMenuService.showContextMenu({ From a43fc5fcb907b313765068f5ee3c641207a71bcb Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 4 Dec 2019 10:39:39 +0100 Subject: [PATCH 133/255] increase debug toolbar z-index --- src/vs/workbench/contrib/debug/browser/media/debugToolBar.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css b/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css index 56d48297c37..70847c6647a 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css @@ -5,7 +5,7 @@ .monaco-workbench .debug-toolbar { position: absolute; - z-index: 200; + z-index: 1000; height: 32px; display: flex; padding-left: 7px; From 7f1975a4fb29d0a788c21f17eb2aad056fec22d8 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Wed, 4 Dec 2019 10:52:25 +0100 Subject: [PATCH 134/255] Revert "Fix layers overlap and cutting at edges. (#85724)" This reverts commit 74ffa7942a107b9cfc45525e508ac601b79aacc1. --- src/vs/workbench/browser/media/part.css | 21 +------------------ .../activitybar/media/activitybarpart.css | 15 +++++++++++++ .../parts/sidebar/media/sidebarpart.css | 4 +++- .../parts/titlebar/media/titlebarpart.css | 16 +++++++++++++- 4 files changed, 34 insertions(+), 22 deletions(-) diff --git a/src/vs/workbench/browser/media/part.css b/src/vs/workbench/browser/media/part.css index 46c88e56c1d..89dd7fe4c9b 100644 --- a/src/vs/workbench/browser/media/part.css +++ b/src/vs/workbench/browser/media/part.css @@ -5,26 +5,7 @@ .monaco-workbench .part { box-sizing: border-box; -} - -.monaco-workbench.windows.chromium .part, -.monaco-workbench.linux.chromium .part { - /* - * Explicitly put the part onto its own layer to help Chrome to - * render the content with LCD-anti-aliasing. By partioning the - * workbench into multiple layers, we can ensure that a bad - * behaving part is not making another part fallback to greyscale - * rendering. - * - * macOS: does not render LCD-anti-aliased. - */ - transform: translate3d(0px, 0px, 0px); - position: relative; -} - -.monaco-workbench.windows.chromium .part:focus-within, -.monaco-workbench.linux.chromium .part:focus-within { - z-index: 1000; + overflow: hidden; } .monaco-workbench .part > .title { diff --git a/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css b/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css index cbc3d14705b..09bb59d2980 100644 --- a/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css +++ b/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css @@ -7,6 +7,21 @@ width: 48px; } +.monaco-workbench.windows.chromium .part.activitybar, +.monaco-workbench.linux.chromium .part.activitybar { + /* + * Explicitly put the part onto its own layer to help Chrome to + * render the content with LCD-anti-aliasing. By partioning the + * workbench into multiple layers, we can ensure that a bad + * behaving part is not making another part fallback to greyscale + * rendering. + * + * macOS: does not render LCD-anti-aliased. + */ + transform: translate3d(0px, 0px, 0px); + overflow: visible; /* when a new layer is created, we need to set overflow visible to avoid clipping the menubar */ +} + .monaco-workbench .activitybar > .content { height: 100%; display: flex; diff --git a/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css b/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css index 8307e81f13e..1e2fad096a3 100644 --- a/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css +++ b/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css @@ -17,7 +17,9 @@ transform: translate3d(0px, 0px, 0px); } - +.monaco-workbench .part.sidebar > .content { + overflow: hidden; +} .monaco-workbench.nosidebar > .part.sidebar { display: none !important; diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index c44fc4bcac5..54c67324b61 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -19,7 +19,21 @@ display: flex; } - +.monaco-workbench.windows .part.titlebar, +.monaco-workbench.linux .part.titlebar { + /* + * Explicitly put the part onto its own layer to help Chrome to + * render the content with LCD-anti-aliasing. By partioning the + * workbench into multiple layers, we can ensure that a bad + * behaving part is not making another part fallback to greyscale + * rendering. + * + * macOS: does not render LCD-anti-aliased. + */ + transform: translate3d(0px, 0px, 0px); + position: relative; + z-index: 1000; /* move the entire titlebar above the workbench, except modals/dialogs */ +} .monaco-workbench .part.titlebar > .titlebar-drag-region { top: 0; From 9fd2603b485fdb874aefeecaa2a83ed4d884505d Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 4 Dec 2019 10:57:18 +0100 Subject: [PATCH 135/255] Remote explorer drop down should respect context keys Fixes #86209 --- src/vs/workbench/contrib/remote/browser/explorerViewItems.ts | 5 +++-- src/vs/workbench/contrib/remote/browser/remote.ts | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/remote/browser/explorerViewItems.ts b/src/vs/workbench/contrib/remote/browser/explorerViewItems.ts index 3cd6c869fca..4026be069da 100644 --- a/src/vs/workbench/contrib/remote/browser/explorerViewItems.ts +++ b/src/vs/workbench/contrib/remote/browser/explorerViewItems.ts @@ -20,6 +20,7 @@ import { startsWith } from 'vs/base/common/strings'; import { isStringArray } from 'vs/base/common/types'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; export interface IRemoteSelectItem extends ISelectOptionItem { authority: string[]; @@ -85,10 +86,10 @@ export class SwitchRemoteViewItem extends SelectActionViewItem { return this.optionsItems[index]; } - static createOptionItems(views: IViewDescriptor[]): IRemoteSelectItem[] { + static createOptionItems(views: IViewDescriptor[], contextKeyService: IContextKeyService): IRemoteSelectItem[] { let options: IRemoteSelectItem[] = []; views.forEach(view => { - if (view.group && startsWith(view.group, 'targets') && view.remoteAuthority) { + if (view.group && startsWith(view.group, 'targets') && view.remoteAuthority && (!view.when || contextKeyService.contextMatchesRules(view.when))) { options.push({ text: view.name, authority: isStringArray(view.remoteAuthority) ? view.remoteAuthority : [view.remoteAuthority] }); } }); diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index 2d7afa657bf..030b5bf6370 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -280,6 +280,7 @@ export class RemoteViewlet extends FilterViewContainerViewlet { @IExtensionService extensionService: IExtensionService, @IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IContextKeyService private readonly contextKeyService: IContextKeyService ) { super(VIEWLET_ID, remoteExplorerService.onDidChangeTargetType, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); } @@ -290,7 +291,7 @@ export class RemoteViewlet extends FilterViewContainerViewlet { public getActionViewItem(action: Action): IActionViewItem | undefined { if (action.id === SwitchRemoteAction.ID) { - return this.instantiationService.createInstance(SwitchRemoteViewItem, action, SwitchRemoteViewItem.createOptionItems(Registry.as(Extensions.ViewsRegistry).getViews(VIEW_CONTAINER))); + return this.instantiationService.createInstance(SwitchRemoteViewItem, action, SwitchRemoteViewItem.createOptionItems(Registry.as(Extensions.ViewsRegistry).getViews(VIEW_CONTAINER), this.contextKeyService)); } return super.getActionViewItem(action); From 510ca01434d037e19119575b00a7e50376d5c3ac Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 4 Dec 2019 11:09:03 +0100 Subject: [PATCH 136/255] Fix #86157 --- tslint.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tslint.json b/tslint.json index a0feadaf2c0..06adddd9c6e 100644 --- a/tslint.json +++ b/tslint.json @@ -75,6 +75,7 @@ "target": "**/vs/base/test/common/**", "restrictions": [ "assert", + "sinon", "vs/nls", "**/vs/base/common/**", "**/vs/base/test/common/**" @@ -101,6 +102,7 @@ "target": "**/vs/base/test/browser/**", "restrictions": [ "assert", + "sinon", "vs/nls", "**/vs/base/{common,browser}/**", "**/vs/base/test/{common,browser}/**" @@ -237,6 +239,7 @@ "target": "**/vs/editor/test/common/**", "restrictions": [ "assert", + "sinon", "vs/nls", "**/vs/base/common/**", "**/vs/platform/*/common/**", @@ -259,6 +262,7 @@ "target": "**/vs/editor/test/browser/**", "restrictions": [ "assert", + "sinon", "vs/nls", "**/vs/base/{common,browser}/**", "**/vs/platform/*/{common,browser}/**", @@ -281,6 +285,7 @@ "target": "**/vs/editor/standalone/test/common/**", "restrictions": [ "assert", + "sinon", "vs/nls", "**/vs/base/common/**", "**/vs/platform/*/common/**", @@ -306,6 +311,7 @@ "target": "**/vs/editor/standalone/test/browser/**", "restrictions": [ "assert", + "sinon", "vs/nls", "**/vs/base/{common,browser}/**", "**/vs/platform/*/{common,browser}/**", @@ -319,6 +325,7 @@ "target": "**/vs/editor/contrib/*/test/**", "restrictions": [ "assert", + "sinon", "vs/nls", "**/vs/base/{common,browser}/**", "**/vs/base/test/{common,browser}/**", From 82e9ac871eda24ab8ff5c9c38c46da6e109486cb Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Wed, 4 Dec 2019 11:19:27 +0100 Subject: [PATCH 137/255] Fixes #86132: Avoid using bitmap in API docs --- src/vs/vscode.proposed.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 62fc13b6430..251d6830ea3 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -144,8 +144,8 @@ declare module 'vscode' { * ``` * * 2. The first transformation is to encode `tokenType` and `tokenModifiers` as integers using the legend. Token types are looked - * up by index, so a `tokenType` value of `1` means `tokenTypes[1]`. Token modifiers are a set and they are looked up by a bitmap, - * so a `tokenModifier` value of `6` is first viewed as a bitmap `0b110`, so it will mean `[tokenModifiers[1], tokenModifiers[2]]` because + * up by index, so a `tokenType` value of `1` means `tokenTypes[1]`. Multiple token modifiers can be set by using bit flags, + * so a `tokenModifier` value of `6` is first viewed as binary `0b110`, which means `[tokenModifiers[1], tokenModifiers[2]]` because * bits 1 and 2 are set. Using this legend, the tokens now are: * ``` * [ { line: 2, startChar: 5, length: 3, tokenType: 1, tokenModifiers: 6 }, // 6 is 0b110 From d82cd8740ba8069f5c8e54182ccd62e4f20ca037 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 4 Dec 2019 11:28:24 +0100 Subject: [PATCH 138/255] highlight root in peek, https://github.com/microsoft/vscode/issues/86128 --- .../contrib/callHierarchy/browser/callHierarchyPeek.ts | 6 +++++- .../contrib/callHierarchy/browser/callHierarchyTree.ts | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts index df5446fd52b..25fe8bade28 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts @@ -325,7 +325,11 @@ export class CallHierarchyTreePeekWidget extends peekView.PeekViewWidget { // set decorations for caller ranges (if in the same file) let decorations: IModelDeltaDecoration[] = []; let fullRange: IRange | undefined; - for (const loc of element.locations) { + let locations = element.locations; + if (!locations) { + locations = [{ uri: element.item.uri, range: element.item.selectionRange }]; + } + for (const loc of locations) { if (loc.uri.toString() === previewUri.toString()) { decorations.push({ range: loc.range, options }); fullRange = !fullRange ? loc.range : Range.plusRange(loc.range, fullRange); diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts index a00b49e0d01..c412f7b992a 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts @@ -17,7 +17,7 @@ import { Range } from 'vs/editor/common/core/range'; export class Call { constructor( readonly item: CallHierarchyItem, - readonly locations: Location[], + readonly locations: Location[] | undefined, readonly model: CallHierarchyModel, readonly parent: Call | undefined ) { } @@ -43,7 +43,7 @@ export class DataSource implements IAsyncDataSource { async getChildren(element: CallHierarchyModel | Call): Promise { if (element instanceof CallHierarchyModel) { - return [new Call(element.root, [], element, undefined)]; + return [new Call(element.root, undefined, element, undefined)]; } const { model, item } = element; From 49a150f406da247ae15118d1c3d3637c8acafb47 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 4 Dec 2019 11:29:58 +0100 Subject: [PATCH 139/255] update references view extension --- build/builtInExtensions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/builtInExtensions.json b/build/builtInExtensions.json index f5c1750229c..e2f11ebf0b3 100644 --- a/build/builtInExtensions.json +++ b/build/builtInExtensions.json @@ -31,7 +31,7 @@ }, { "name": "ms-vscode.references-view", - "version": "0.0.39", + "version": "0.0.40", "repo": "https://github.com/Microsoft/vscode-reference-view", "metadata": { "id": "dc489f46-520d-4556-ae85-1f9eab3c412d", From 59e50ec94ce19f6126e1d81b811acc6ba8de0a52 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 4 Dec 2019 11:45:32 +0100 Subject: [PATCH 140/255] fix #86048 --- src/vs/editor/contrib/gotoSymbol/symbolNavigation.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/vs/editor/contrib/gotoSymbol/symbolNavigation.ts b/src/vs/editor/contrib/gotoSymbol/symbolNavigation.ts index 9d9bc68a558..879f3b9aaf9 100644 --- a/src/vs/editor/contrib/gotoSymbol/symbolNavigation.ts +++ b/src/vs/editor/contrib/gotoSymbol/symbolNavigation.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ReferencesModel, OneReference } from 'vs/editor/contrib/gotoSymbol/referencesModel'; -import { RawContextKey, IContextKeyService, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { RawContextKey, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { createDecorator, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { KeybindingWeight, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -155,10 +155,7 @@ registerEditorCommand(new class extends EditorCommand { constructor() { super({ id: 'editor.gotoNextSymbolFromResult', - precondition: ContextKeyExpr.and( - ctxHasSymbols, - ContextKeyExpr.equals('config.editor.gotoLocation.multiple', 'goto') - ), + precondition: ctxHasSymbols, kbOpts: { weight: KeybindingWeight.EditorContrib, primary: KeyCode.F12 From 7d7cb80a87dcc8a79907720feba43d744340e2bf Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 4 Dec 2019 12:34:04 +0100 Subject: [PATCH 141/255] fixes #86113 --- src/vs/workbench/contrib/debug/browser/debug.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index a32eb210b31..2ee642b2c86 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -84,7 +84,7 @@ class OpenDebugPanelAction extends TogglePanelAction { Registry.as(ViewletExtensions.Viewlets).registerViewlet(ViewletDescriptor.create( DebugViewlet, VIEWLET_ID, - nls.localize('debugAndRun', "Debug And Run"), + nls.localize('debugAndRun', "Debug and Run"), 'codicon-debug', 3 )); From 312701fe711a1f2b840f9887e564530b690d4eae Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 4 Dec 2019 12:51:30 +0100 Subject: [PATCH 142/255] fixes #86111 --- .../contrib/debug/browser/startView.ts | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/startView.ts b/src/vs/workbench/contrib/debug/browser/startView.ts index a390eb4c524..bfade3e4b0e 100644 --- a/src/vs/workbench/contrib/debug/browser/startView.ts +++ b/src/vs/workbench/contrib/debug/browser/startView.ts @@ -21,6 +21,8 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { equals } from 'vs/base/common/arrays'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { KeyCode } from 'vs/base/common/keyCodes'; const $ = dom.$; export class StartView extends ViewletPane { @@ -72,16 +74,12 @@ export class StartView extends ViewletPane { const setSecondMessage = () => { secondMessageElement.textContent = localize('specifyHowToRun', "To futher configure Debug and Run"); - const clickElement = $('span.click'); - clickElement.textContent = localize('configure', " create a launch.json file."); - clickElement.onclick = () => this.commandService.executeCommand(ConfigureAction.ID); + const clickElement = this.createClickElement(localize('configure', " create a launch.json file."), () => this.commandService.executeCommand(ConfigureAction.ID)); this.secondMessageContainer.appendChild(clickElement); }; const setSecondMessageWithFolder = () => { secondMessageElement.textContent = localize('noLaunchConfiguration', "To futher configure Debug and Run, "); - const clickElement = $('span.click'); - clickElement.textContent = localize('openFolder', " open a folder"); - clickElement.onclick = () => this.dialogService.pickFolderAndOpen({ forceNewWindow: false }); + const clickElement = this.createClickElement(localize('openFolder', " open a folder"), () => this.dialogService.pickFolderAndOpen({ forceNewWindow: false })); this.secondMessageContainer.appendChild(clickElement); const moreText = $('span.moreText'); @@ -106,10 +104,7 @@ export class StartView extends ViewletPane { } if (!enabled && emptyWorkbench) { - const clickElement = $('span.click'); - clickElement.textContent = localize('openFile', "Open a file"); - clickElement.onclick = () => this.dialogService.pickFileAndOpen({ forceNewWindow: false }); - + const clickElement = this.createClickElement(localize('openFile', "Open a file"), () => this.dialogService.pickFileAndOpen({ forceNewWindow: false })); this.firstMessageContainer.appendChild(clickElement); const firstMessageElement = $('span'); this.firstMessageContainer.appendChild(firstMessageElement); @@ -121,6 +116,21 @@ export class StartView extends ViewletPane { } } + private createClickElement(textContent: string, action: () => any): HTMLSpanElement { + const clickElement = $('span.click'); + clickElement.textContent = textContent; + clickElement.onclick = action; + clickElement.tabIndex = 0; + clickElement.onkeyup = (e) => { + const keyboardEvent = new StandardKeyboardEvent(e); + if (keyboardEvent.keyCode === KeyCode.Enter || (keyboardEvent.keyCode === KeyCode.Space)) { + action(); + } + }; + + return clickElement; + } + protected renderBody(container: HTMLElement): void { this.firstMessageContainer = $('.top-section'); container.appendChild(this.firstMessageContainer); From 60d44109226c3f2d00ad32e89678292c1d51fd27 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 4 Dec 2019 14:16:00 +0100 Subject: [PATCH 143/255] Fix #85638 --- src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css index 8dde95a5b7e..560bb371cbf 100644 --- a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css +++ b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css @@ -51,7 +51,6 @@ .monaco-workbench .part.statusbar > .left-items { flex-grow: 1; /* left items push right items to the far right end */ - overflow: hidden; /* Hide the overflowing entries */ } .monaco-workbench .part.statusbar > .items-container > .statusbar-item { From 8e5169a6bfedfa23602293330a0ca70edd413c3f Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 4 Dec 2019 14:27:40 +0100 Subject: [PATCH 144/255] fixes #85977 --- .../contrib/debug/browser/debugConfigurationManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index eecb9c57d6c..f67c70db9d8 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -80,7 +80,7 @@ export class ConfigurationManager implements IConfigurationManager { const previousSelectedRoot = this.storageService.get(DEBUG_SELECTED_ROOT, StorageScope.WORKSPACE); const previousSelectedLaunch = this.launches.filter(l => l.uri.toString() === previousSelectedRoot).pop(); this.debugConfigurationTypeContext = CONTEXT_DEBUG_CONFIGURATION_TYPE.bindTo(contextKeyService); - if (previousSelectedLaunch) { + if (previousSelectedLaunch && previousSelectedLaunch.getConfigurationNames().length) { this.selectConfiguration(previousSelectedLaunch, this.storageService.get(DEBUG_SELECTED_CONFIG_NAME_KEY, StorageScope.WORKSPACE)); } else if (this.launches.length > 0) { const rootUri = historyService.getLastActiveWorkspaceRoot(); From 6ee34c59734ef4a2b0323b9cbb72ac1d0d092912 Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 4 Dec 2019 14:34:28 +0100 Subject: [PATCH 145/255] fixes #86112 --- src/vs/workbench/contrib/debug/browser/debugService.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 56e0f0c8a42..24dc3d59a8c 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -232,7 +232,8 @@ export class DebugService implements IDebugService { if (this.previousState !== state) { this.debugState.set(getStateLabel(state)); this.inDebugMode.set(state !== State.Inactive); - this.debugUx.set(!!(state !== State.Inactive || this.configurationManager.selectedConfiguration.name) ? 'default' : 'simple'); + // Only show the simple ux if debug is not yet started and if no launch.json exists + this.debugUx.set(((state !== State.Inactive && state !== State.Initializing) || this.configurationManager.selectedConfiguration.name) ? 'default' : 'simple'); this.previousState = state; this._onDidChangeState.fire(state); } From 0dbe568b59c9fd213bddc5ddc730a4892f3b011c Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 4 Dec 2019 14:53:02 +0100 Subject: [PATCH 146/255] fixes #86103 --- .../contrib/customEditor/browser/commands.ts | 3 ++- .../browser/externalTerminal.contribution.ts | 4 ++-- .../contrib/files/browser/fileCommands.ts | 12 +++++++----- src/vs/workbench/contrib/files/browser/files.ts | 16 ++++++---------- .../electron-browser/fileActions.contribution.ts | 3 ++- .../search/browser/search.contribution.ts | 2 +- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/vs/workbench/contrib/customEditor/browser/commands.ts b/src/vs/workbench/contrib/customEditor/browser/commands.ts index cf1118961f2..7b384f6c7d1 100644 --- a/src/vs/workbench/contrib/customEditor/browser/commands.ts +++ b/src/vs/workbench/contrib/customEditor/browser/commands.ts @@ -20,6 +20,7 @@ import { CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, CONTEXT_HAS_CUSTOM_EDITORS, import { getMultiSelectedResources } from 'vs/workbench/contrib/files/browser/files'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IExplorerService } from 'vs/workbench/contrib/files/common/files'; const viewCategory = nls.localize('viewCategory', "View"); @@ -34,7 +35,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ when: EditorContextKeys.focus.toNegated(), handler: async (accessor: ServicesAccessor, resource: URI | object) => { const editorService = accessor.get(IEditorService); - const resources = getMultiSelectedResources(resource, accessor.get(IListService), editorService); + const resources = getMultiSelectedResources(resource, accessor.get(IListService), editorService, accessor.get(IExplorerService)); const targetResource = firstOrDefault(resources); if (!targetResource) { return; diff --git a/src/vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution.ts b/src/vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution.ts index e4a7454fb10..92c391fad15 100644 --- a/src/vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution.ts +++ b/src/vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution.ts @@ -24,6 +24,7 @@ import { distinct } from 'vs/base/common/arrays'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { optional } from 'vs/platform/instantiation/common/instantiation'; +import { IExplorerService } from 'vs/workbench/contrib/files/common/files'; const OPEN_IN_TERMINAL_COMMAND_ID = 'openInTerminal'; @@ -37,7 +38,7 @@ CommandsRegistry.registerCommand({ const integratedTerminalService = accessor.get(IIntegratedTerminalService); const remoteAgentService = accessor.get(IRemoteAgentService); - const resources = getMultiSelectedResources(resource, accessor.get(IListService), editorService); + const resources = getMultiSelectedResources(resource, accessor.get(IListService), editorService, accessor.get(IExplorerService)); return fileService.resolveAll(resources.map(r => ({ resource: r }))).then(async stats => { const targets = distinct(stats.filter(data => data.success)); // Always use integrated terminal when using a remote @@ -162,4 +163,3 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { command: openConsoleCommand, when: ResourceContextKey.Scheme.isEqualTo(Schemas.vscodeRemote) }); - diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index f09033db891..3dcc31af8e9 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -119,7 +119,8 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ const editorService = accessor.get(IEditorService); const listService = accessor.get(IListService); const fileService = accessor.get(IFileService); - const resources = getMultiSelectedResources(resource, listService, editorService); + const explorerService = accessor.get(IExplorerService); + const resources = getMultiSelectedResources(resource, listService, editorService, explorerService); // Set side input if (resources.length) { @@ -201,7 +202,8 @@ CommandsRegistry.registerCommand({ id: COMPARE_SELECTED_COMMAND_ID, handler: (accessor, resource: URI | object) => { const editorService = accessor.get(IEditorService); - const resources = getMultiSelectedResources(resource, accessor.get(IListService), editorService); + const explorerService = accessor.get(IExplorerService); + const resources = getMultiSelectedResources(resource, accessor.get(IListService), editorService, explorerService); if (resources.length === 2) { return editorService.openEditor({ @@ -251,7 +253,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ }, id: COPY_PATH_COMMAND_ID, handler: async (accessor, resource: URI | object) => { - const resources = getMultiSelectedResources(resource, accessor.get(IListService), accessor.get(IEditorService)); + const resources = getMultiSelectedResources(resource, accessor.get(IListService), accessor.get(IEditorService), accessor.get(IExplorerService)); await resourcesToClipboard(resources, false, accessor.get(IClipboardService), accessor.get(INotificationService), accessor.get(ILabelService)); } }); @@ -265,7 +267,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ }, id: COPY_RELATIVE_PATH_COMMAND_ID, handler: async (accessor, resource: URI | object) => { - const resources = getMultiSelectedResources(resource, accessor.get(IListService), accessor.get(IEditorService)); + const resources = getMultiSelectedResources(resource, accessor.get(IListService), accessor.get(IEditorService), accessor.get(IExplorerService)); await resourcesToClipboard(resources, true, accessor.get(IClipboardService), accessor.get(INotificationService), accessor.get(ILabelService)); } }); @@ -488,7 +490,7 @@ CommandsRegistry.registerCommand({ const workspaceEditingService = accessor.get(IWorkspaceEditingService); const contextService = accessor.get(IWorkspaceContextService); const workspace = contextService.getWorkspace(); - const resources = getMultiSelectedResources(resource, accessor.get(IListService), accessor.get(IEditorService)).filter(r => + const resources = getMultiSelectedResources(resource, accessor.get(IListService), accessor.get(IEditorService), accessor.get(IExplorerService)).filter(r => // Need to verify resources are workspaces since multi selection can trigger this command on some non workspace resources workspace.folders.some(f => isEqual(f.uri, r)) ); diff --git a/src/vs/workbench/contrib/files/browser/files.ts b/src/vs/workbench/contrib/files/browser/files.ts index 7746773311f..c501bd0f486 100644 --- a/src/vs/workbench/contrib/files/browser/files.ts +++ b/src/vs/workbench/contrib/files/browser/files.ts @@ -5,7 +5,7 @@ import { URI } from 'vs/base/common/uri'; import { IListService } from 'vs/platform/list/browser/listService'; -import { OpenEditor } from 'vs/workbench/contrib/files/common/files'; +import { OpenEditor, IExplorerService } from 'vs/workbench/contrib/files/common/files'; import { toResource, SideBySideEditor, IEditorIdentifier } from 'vs/workbench/common/editor'; import { List } from 'vs/base/browser/ui/list/listWidget'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -53,19 +53,15 @@ export function getResourceForCommand(resource: URI | object | undefined, listSe return editorService.activeEditor ? toResource(editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }) : undefined; } -export function getMultiSelectedResources(resource: URI | object | undefined, listService: IListService, editorService: IEditorService): Array { +export function getMultiSelectedResources(resource: URI | object | undefined, listService: IListService, editorService: IEditorService, explorerService: IExplorerService): Array { const list = listService.lastFocusedList; if (list?.getHTMLElement() === document.activeElement) { // Explorer if (list instanceof AsyncDataTree) { - const selection = list.getSelection().map((fs: ExplorerItem) => fs.resource); - const focusedElements = list.getFocus(); - const focus = focusedElements.length ? focusedElements[0] : undefined; - const mainUriStr = URI.isUri(resource) ? resource.toString() : focus instanceof ExplorerItem ? focus.resource.toString() : undefined; - // If the resource is passed it has to be a part of the returned context. - // We only respect the selection if it contains the focused element. - if (selection.some(s => URI.isUri(s) && s.toString() === mainUriStr)) { - return selection; + // Explorer + const context = explorerService.getContext(true); + if (context.length) { + return context.map(c => c.resource); } } diff --git a/src/vs/workbench/contrib/files/electron-browser/fileActions.contribution.ts b/src/vs/workbench/contrib/files/electron-browser/fileActions.contribution.ts index 9c63d009f8d..fc370f6d577 100644 --- a/src/vs/workbench/contrib/files/electron-browser/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/electron-browser/fileActions.contribution.ts @@ -24,6 +24,7 @@ import { appendToCommandPalette, appendEditorTitleContextMenuItem } from 'vs/wor import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { ShowOpenedFileInNewWindow } from 'vs/workbench/contrib/files/browser/fileActions'; +import { IExplorerService } from 'vs/workbench/contrib/files/common/files'; const REVEAL_IN_OS_COMMAND_ID = 'revealFileInOS'; const REVEAL_IN_OS_LABEL = isWindows ? nls.localize('revealInWindows', "Reveal in Explorer") : isMacintosh ? nls.localize('revealInMac', "Reveal in Finder") : nls.localize('openContainer', "Open Containing Folder"); @@ -37,7 +38,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_R }, handler: (accessor: ServicesAccessor, resource: URI | object) => { - const resources = getMultiSelectedResources(resource, accessor.get(IListService), accessor.get(IEditorService)); + const resources = getMultiSelectedResources(resource, accessor.get(IListService), accessor.get(IEditorService), accessor.get(IExplorerService)); revealResourcesInOS(resources, accessor.get(IElectronService), accessor.get(INotificationService), accessor.get(IWorkspaceContextService)); } }); diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 76763901606..c487eddae0b 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -396,7 +396,7 @@ const searchInFolderCommand: ICommandHandler = (accessor, resource?: URI) => { const panelService = accessor.get(IPanelService); const fileService = accessor.get(IFileService); const configurationService = accessor.get(IConfigurationService); - const resources = getMultiSelectedResources(resource, listService, accessor.get(IEditorService)); + const resources = getMultiSelectedResources(resource, listService, accessor.get(IEditorService), accessor.get(IExplorerService)); return openSearchView(viewletService, panelService, configurationService, true).then(searchView => { if (resources && resources.length && searchView) { From 6b0fce926bde0005e612c2c133e79bc7aa1a1106 Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Wed, 4 Dec 2019 14:56:54 +0100 Subject: [PATCH 147/255] Add missing properties (fixes microsoft/vscode-remote-release#1961) --- .../schemas/attachContainer.schema.json | 4 ++++ .../configuration-editing/schemas/devContainer.schema.json | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/extensions/configuration-editing/schemas/attachContainer.schema.json b/extensions/configuration-editing/schemas/attachContainer.schema.json index ba0df4b33dc..65f79624518 100644 --- a/extensions/configuration-editing/schemas/attachContainer.schema.json +++ b/extensions/configuration-editing/schemas/attachContainer.schema.json @@ -18,6 +18,10 @@ "type": "integer" } }, + "settings": { + "$ref": "vscode://schemas/settings/machine", + "description": "Machine specific settings that should be copied into the container." + }, "remoteEnv": { "type": "object", "additionalProperties": { diff --git a/extensions/configuration-editing/schemas/devContainer.schema.json b/extensions/configuration-editing/schemas/devContainer.schema.json index 631c58e82b5..710c2a26c60 100644 --- a/extensions/configuration-editing/schemas/devContainer.schema.json +++ b/extensions/configuration-editing/schemas/devContainer.schema.json @@ -84,6 +84,13 @@ "type": "boolean", "description": "Controls whether on Linux the container's user should be updated with the local user's UID and GID. On by default." }, + "mounts": { + "type": "array", + "description": "Mount points to set up when creating the container. See Docker's documentation for the --mount option for the supported syntax.", + "items": { + "type": "string" + } + }, "runArgs": { "type": "array", "description": "The arguments required when starting in the container.", From af60c01ff0db9ee482f1e146ddabddf32fa87bd5 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 4 Dec 2019 15:05:33 +0100 Subject: [PATCH 148/255] Fix #86135 --- .../contrib/markers/browser/markersPanel.ts | 47 +++++++++++++++++-- .../contrib/markers/browser/messages.ts | 1 + 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/markers/browser/markersPanel.ts b/src/vs/workbench/contrib/markers/browser/markersPanel.ts index 321d56b444e..f2159a61fea 100644 --- a/src/vs/workbench/contrib/markers/browser/markersPanel.ts +++ b/src/vs/workbench/contrib/markers/browser/markersPanel.ts @@ -37,7 +37,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { Separator, ActionViewItem, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { StandardKeyboardEvent, IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { domEvent } from 'vs/base/browser/event'; import { ResourceLabels } from 'vs/workbench/browser/labels'; import { IMarker } from 'vs/platform/markers/common/markers'; @@ -46,6 +46,7 @@ import { MementoObject } from 'vs/workbench/common/memento'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { PANEL_BACKGROUND } from 'vs/workbench/common/theme'; +import { KeyCode } from 'vs/base/common/keyCodes'; function createResourceMarkersIterator(resourceMarkers: ResourceMarkers): Iterator> { const markersIt = Iterator.fromArray(resourceMarkers.markers); @@ -520,10 +521,14 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { if (filtered === 0) { this.messageBoxContainer.style.display = 'block'; this.messageBoxContainer.setAttribute('tabIndex', '0'); - if (total > 0) { - this.renderFilteredByFilterMessage(this.messageBoxContainer); + if (this.filterAction.activeFile) { + this.renderFilterMessageForActiveFile(this.messageBoxContainer); } else { - this.renderNoProblemsMessage(this.messageBoxContainer); + if (total > 0) { + this.renderFilteredByFilterMessage(this.messageBoxContainer); + } else { + this.renderNoProblemsMessage(this.messageBoxContainer); + } } } else { this.messageBoxContainer.style.display = 'none'; @@ -536,18 +541,52 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { } } + private renderFilterMessageForActiveFile(container: HTMLElement): void { + if (this.currentActiveResource && this.markersWorkbenchService.markersModel.getResourceMarkers(this.currentActiveResource)) { + this.renderFilteredByFilterMessage(container); + } else { + this.renderNoProblemsMessageForActiveFile(container); + } + } + private renderFilteredByFilterMessage(container: HTMLElement) { const span1 = dom.append(container, dom.$('span')); span1.textContent = Messages.MARKERS_PANEL_NO_PROBLEMS_FILTERS; + const link = dom.append(container, dom.$('a.messageAction')); + link.textContent = localize('clearFilter', "Clear Filters"); + link.setAttribute('tabIndex', '0'); + const span2 = dom.append(container, dom.$('span')); + span2.textContent = '.'; + dom.addStandardDisposableListener(link, dom.EventType.CLICK, () => this.clearFilters()); + dom.addStandardDisposableListener(link, dom.EventType.KEY_DOWN, (e: IKeyboardEvent) => { + if (e.equals(KeyCode.Enter) || e.equals(KeyCode.Space)) { + this.clearFilters(); + e.stopPropagation(); + } + }); this.ariaLabelElement.setAttribute('aria-label', Messages.MARKERS_PANEL_NO_PROBLEMS_FILTERS); } + private renderNoProblemsMessageForActiveFile(container: HTMLElement) { + const span = dom.append(container, dom.$('span')); + span.textContent = Messages.MARKERS_PANEL_NO_PROBLEMS_ACTIVE_FILE_BUILT; + this.ariaLabelElement.setAttribute('aria-label', Messages.MARKERS_PANEL_NO_PROBLEMS_ACTIVE_FILE_BUILT); + } + private renderNoProblemsMessage(container: HTMLElement) { const span = dom.append(container, dom.$('span')); span.textContent = Messages.MARKERS_PANEL_NO_PROBLEMS_BUILT; this.ariaLabelElement.setAttribute('aria-label', Messages.MARKERS_PANEL_NO_PROBLEMS_BUILT); } + private clearFilters(): void { + this.filterAction.filterText = ''; + this.filterAction.excludedFiles = false; + this.filterAction.showErrors = true; + this.filterAction.showWarnings = true; + this.filterAction.showInfos = true; + } + private autoReveal(focus: boolean = false): void { // No need to auto reveal if active file filter is on if (this.filterAction.activeFile) { diff --git a/src/vs/workbench/contrib/markers/browser/messages.ts b/src/vs/workbench/contrib/markers/browser/messages.ts index 438ebcd5588..7275dc27c43 100644 --- a/src/vs/workbench/contrib/markers/browser/messages.ts +++ b/src/vs/workbench/contrib/markers/browser/messages.ts @@ -21,6 +21,7 @@ export default class Messages { public static MARKERS_PANEL_TITLE_PROBLEMS: string = nls.localize('markers.panel.title.problems', "Problems"); public static MARKERS_PANEL_NO_PROBLEMS_BUILT: string = nls.localize('markers.panel.no.problems.build', "No problems have been detected in the workspace so far."); + public static MARKERS_PANEL_NO_PROBLEMS_ACTIVE_FILE_BUILT: string = nls.localize('markers.panel.no.problems.activeFile.build', "No problems have been detected in the current file so far."); public static MARKERS_PANEL_NO_PROBLEMS_FILTERS: string = nls.localize('markers.panel.no.problems.filters', "No results found with provided filter criteria."); public static MARKERS_PANEL_ACTION_TOOLTIP_MORE_FILTERS: string = nls.localize('markers.panel.action.moreFilters', "More Filters..."); From d75b73b1c9e7efa35cd306f950a580cd84cacbc2 Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 4 Dec 2019 15:05:50 +0100 Subject: [PATCH 149/255] User canceled a download. In case there were multiple files selected we should cancel the remainder of the prompts fixes #86100 --- src/vs/workbench/contrib/files/browser/fileActions.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 8fc60ca17e9..43beeb606fd 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -980,7 +980,12 @@ const downloadFileHandler = (accessor: ServicesAccessor) => { const explorerService = accessor.get(IExplorerService); const stats = explorerService.getContext(true); + let canceled = false; stats.forEach(async s => { + if (canceled) { + return; + } + if (isWeb) { if (!s.isDirectory) { triggerDownload(asDomUri(s.resource), s.name); @@ -999,6 +1004,9 @@ const downloadFileHandler = (accessor: ServicesAccessor) => { }); if (destination) { await textFileService.copy(s.resource, destination); + } else { + // User canceled a download. In case there were multiple files selected we should cancel the remainder of the prompts #86100 + canceled = true; } } }); From c03d5ed006b241fcd77ee78696a0fbbcc65473d4 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 4 Dec 2019 15:16:05 +0100 Subject: [PATCH 150/255] Add 'Documentation' to ? hover in remote explorer --- src/vs/workbench/contrib/remote/browser/remote.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index 030b5bf6370..70ab4f4fc52 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -236,7 +236,7 @@ class IssueReporterItem extends HelpItemBase { class HelpAction extends Action { static readonly ID = 'remote.explorer.help'; - static readonly LABEL = nls.localize('remote.explorer.help', "Help and Feedback"); + static readonly LABEL = nls.localize('remote.explorer.help', "Help, Documentation, and Feedback"); private helpModel: HelpModel; constructor(id: string, From ef121d2d876e98114bf92062a779f3773dd916fc Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Wed, 4 Dec 2019 14:43:17 +0100 Subject: [PATCH 151/255] Remove incomplete checks (fixes #85844) --- src/vs/base/browser/ui/iconLabel/iconLabel.ts | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/vs/base/browser/ui/iconLabel/iconLabel.ts b/src/vs/base/browser/ui/iconLabel/iconLabel.ts index 425f657432c..30b4246b9cf 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabel.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabel.ts @@ -163,18 +163,11 @@ export class IconLabel extends Disposable { class Label { - private label: string | string[] | undefined = undefined; private singleLabel: HTMLElement | undefined = undefined; constructor(private container: HTMLElement) { } setLabel(label: string | string[], options?: IIconLabelValueOptions): void { - if (this.label === label) { - return; - } - - this.label = label; - if (typeof label === 'string') { if (!this.singleLabel) { this.container.innerHTML = ''; @@ -224,18 +217,11 @@ function splitMatches(labels: string[], separator: string, matches: IMatch[] | u class LabelWithHighlights { - private label: string | string[] | undefined = undefined; private singleLabel: HighlightedLabel | undefined = undefined; constructor(private container: HTMLElement, private supportCodicons: boolean) { } setLabel(label: string | string[], options?: IIconLabelValueOptions): void { - if (this.label === label) { - return; - } - - this.label = label; - if (typeof label === 'string') { if (!this.singleLabel) { this.container.innerHTML = ''; From 669d0ab44e4110de5517d8c87b0b306c89bfba3a Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 4 Dec 2019 15:12:42 +0100 Subject: [PATCH 152/255] [json] make result limit configurable. Fixes #84259 --- extensions/json-language-features/client/src/jsonMain.ts | 4 +++- extensions/json-language-features/package.json | 5 +++++ extensions/json-language-features/package.nls.json | 3 ++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/extensions/json-language-features/client/src/jsonMain.ts b/extensions/json-language-features/client/src/jsonMain.ts index 23c42f795c9..cb318b46518 100644 --- a/extensions/json-language-features/client/src/jsonMain.ts +++ b/extensions/json-language-features/client/src/jsonMain.ts @@ -351,6 +351,8 @@ function getSchemaAssociation(_context: ExtensionContext): ISchemaAssociations { function getSettings(): Settings { let httpSettings = workspace.getConfiguration('http'); + let resultLimit: number = Math.trunc(Math.max(0, Number(workspace.getConfiguration().get('json.maxItemsComputed')))) || 5000; + let settings: Settings = { http: { proxy: httpSettings.get('proxy'), @@ -358,7 +360,7 @@ function getSettings(): Settings { }, json: { schemas: [], - resultLimit: 5000 + resultLimit } }; let schemaSettingsById: { [schemaId: string]: JSONSchemaSettings } = Object.create(null); diff --git a/extensions/json-language-features/package.json b/extensions/json-language-features/package.json index 69d898b9e7f..09f7c6c1643 100644 --- a/extensions/json-language-features/package.json +++ b/extensions/json-language-features/package.json @@ -90,6 +90,11 @@ "default": true, "description": "%json.colorDecorators.enable.desc%", "deprecationMessage": "%json.colorDecorators.enable.deprecationMessage%" + }, + "json.maxItemsComputed": { + "type": "number", + "default": 5000, + "description": "%json.maxItemsComputed.desc%" } } }, diff --git a/extensions/json-language-features/package.nls.json b/extensions/json-language-features/package.nls.json index c61e7b70e8f..5d132ccd776 100644 --- a/extensions/json-language-features/package.nls.json +++ b/extensions/json-language-features/package.nls.json @@ -11,5 +11,6 @@ "json.colorDecorators.enable.desc": "Enables or disables color decorators", "json.colorDecorators.enable.deprecationMessage": "The setting `json.colorDecorators.enable` has been deprecated in favor of `editor.colorDecorators`.", "json.schemaResolutionErrorMessage": "Unable to resolve schema.", - "json.clickToRetry": "Click to retry." + "json.clickToRetry": "Click to retry.", + "json.maxItemsComputed.desc": "The maximum number of outline symbols and folding regions computed (limited for performance reasons)." } From 235ceb4d387bdd32ff3830d0f5aeb225ae88c6d8 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 4 Dec 2019 15:40:58 +0100 Subject: [PATCH 153/255] [json] update service --- extensions/json-language-features/server/package.json | 2 +- extensions/json-language-features/server/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/extensions/json-language-features/server/package.json b/extensions/json-language-features/server/package.json index b4992717f5b..64a9c5f5eba 100644 --- a/extensions/json-language-features/server/package.json +++ b/extensions/json-language-features/server/package.json @@ -14,7 +14,7 @@ "dependencies": { "jsonc-parser": "^2.2.0", "request-light": "^0.2.5", - "vscode-json-languageservice": "^3.4.8", + "vscode-json-languageservice": "^3.4.9", "vscode-languageserver": "^6.0.0-next.3", "vscode-uri": "^2.1.1" }, diff --git a/extensions/json-language-features/server/yarn.lock b/extensions/json-language-features/server/yarn.lock index b5fcaf44bab..7ebc93341c6 100644 --- a/extensions/json-language-features/server/yarn.lock +++ b/extensions/json-language-features/server/yarn.lock @@ -80,10 +80,10 @@ request-light@^0.2.5: https-proxy-agent "^2.2.3" vscode-nls "^4.1.1" -vscode-json-languageservice@^3.4.8: - version "3.4.8" - resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-3.4.8.tgz#2fc14e0a2603ed704ab57e29f3bcce75106be2bd" - integrity sha512-8h3VjUU4r37LVleIV7YGYs6MlnwHs4qR002cJsL3Z/ebrKzgab1OkwzGocAAJY21rnLhVy4KjnIy7oN1XGPlqA== +vscode-json-languageservice@^3.4.9: + version "3.4.9" + resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-3.4.9.tgz#7ce485bb0f9a07b4d879c988baac9be2222909ad" + integrity sha512-4VCpZ9ooea/Zc/MTnj1ccc9C7rqcoinKVQLhLoi6jw6yueSf4y4tg/YIUiPPVMlEAG7ZCPS+NVmqxisQ+mOsSw== dependencies: jsonc-parser "^2.2.0" vscode-languageserver-textdocument "^1.0.0-next.4" From efd633a2fb1a954eeb4469a38c8e69610ad3e9a1 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 4 Dec 2019 15:55:48 +0100 Subject: [PATCH 154/255] Fix #86062 --- .../userDataSync/common/userDataSyncUtil.ts | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts b/src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts index 237e55c17e4..947f5521b8b 100644 --- a/src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts +++ b/src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts @@ -10,6 +10,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { FormattingOptions } from 'vs/base/common/jsonFormatter'; import { URI } from 'vs/base/common/uri'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { ITextResourcePropertiesService, ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; class UserDataSyncUtilService implements IUserDataSyncUtilService { @@ -18,6 +19,8 @@ class UserDataSyncUtilService implements IUserDataSyncUtilService { constructor( @IKeybindingService private readonly keybindingsService: IKeybindingService, @ITextModelService private readonly textModelService: ITextModelService, + @ITextResourcePropertiesService private readonly textResourcePropertiesService: ITextResourcePropertiesService, + @ITextResourceConfigurationService private readonly textResourceConfigurationService: ITextResourceConfigurationService, ) { } public async resolveUserBindings(userBindings: string[]): Promise> { @@ -29,11 +32,19 @@ class UserDataSyncUtilService implements IUserDataSyncUtilService { } async resolveFormattingOptions(resource: URI): Promise { - const modelReference = await this.textModelService.createModelReference(resource); - const { insertSpaces, tabSize } = modelReference.object.textEditorModel.getOptions(); - const eol = modelReference.object.textEditorModel.getEOL(); - modelReference.dispose(); - return { eol, insertSpaces, tabSize }; + try { + const modelReference = await this.textModelService.createModelReference(resource); + const { insertSpaces, tabSize } = modelReference.object.textEditorModel.getOptions(); + const eol = modelReference.object.textEditorModel.getEOL(); + modelReference.dispose(); + return { eol, insertSpaces, tabSize }; + } catch (e) { + } + return { + eol: this.textResourcePropertiesService.getEOL(resource), + insertSpaces: this.textResourceConfigurationService.getValue(resource, 'editor.insertSpaces'), + tabSize: this.textResourceConfigurationService.getValue(resource, 'editor.tabSize') + }; } } From 1ce943831d1c520ac974367f19e86870c509e658 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 4 Dec 2019 15:57:48 +0100 Subject: [PATCH 155/255] [json] mention setting to change limit --- .../json-language-features/client/src/jsonMain.ts | 10 ++++++++++ extensions/json-language-features/server/README.md | 11 +++++++++++ .../server/src/jsonServerMain.ts | 6 +++++- 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/extensions/json-language-features/client/src/jsonMain.ts b/extensions/json-language-features/client/src/jsonMain.ts index cb318b46518..32c0d422643 100644 --- a/extensions/json-language-features/client/src/jsonMain.ts +++ b/extensions/json-language-features/client/src/jsonMain.ts @@ -44,6 +44,10 @@ namespace SchemaAssociationNotification { export const type: NotificationType = new NotificationType('json/schemaAssociations'); } +namespace ResultLimitReachedNotification { + export const type: NotificationType = new NotificationType('json/resultLimitReached'); +} + interface IPackageInfo { name: string; version: string; @@ -270,6 +274,12 @@ export function activate(context: ExtensionContext) { updateFormatterRegistration(); toDispose.push({ dispose: () => rangeFormatting && rangeFormatting.dispose() }); toDispose.push(workspace.onDidChangeConfiguration(e => e.affectsConfiguration('html.format.enable') && updateFormatterRegistration())); + + + client.onNotification(ResultLimitReachedNotification.type, message => { + window.showInformationMessage(`${message}\nUse setting 'json.maxItemsComputed' to configure the limit.`); + }); + }); let languageConfiguration: LanguageConfiguration = { diff --git a/extensions/json-language-features/server/README.md b/extensions/json-language-features/server/README.md index 71356e3c441..66fd8437d6a 100644 --- a/extensions/json-language-features/server/README.md +++ b/extensions/json-language-features/server/README.md @@ -63,6 +63,7 @@ The server supports the following settings: - `fileMatch`: an array of file names or paths (separated by `/`). `*` can be used as a wildcard. - `url`: The URL of the schema, optional when also a schema is provided. - `schema`: The schema content. + - `resultLimit`: The max number foldig ranges and otline symbols to be computed (for performance reasons) ```json { @@ -153,6 +154,16 @@ Notification: - method: 'json/schemaContent' - params: `string` the URL of the schema that has changed. +### Item Limit + +If the setting `resultLimit` is set, the JSON language server will limit the number of folding ranges and document symbols computed. +When the limit is reached, a notification `json/resultLimitReached` is sent that can be shown that camn be shown to the user. + +Notification: +- method: 'json/resultLimitReached' +- params: a human readable string to show to the user. + + ## Try The JSON language server is shipped with [Visual Studio Code](https://code.visualstudio.com/) as part of the built-in VSCode extension `json-language-features`. The server is started when the first JSON file is opened. The [VSCode JSON documentation](https://code.visualstudio.com/docs/languages/json) for detailed information on the user experience and has more information on how to configure the language support. diff --git a/extensions/json-language-features/server/src/jsonServerMain.ts b/extensions/json-language-features/server/src/jsonServerMain.ts index 975f27b481d..4b058d12350 100644 --- a/extensions/json-language-features/server/src/jsonServerMain.ts +++ b/extensions/json-language-features/server/src/jsonServerMain.ts @@ -35,6 +35,10 @@ namespace SchemaContentChangeNotification { export const type: NotificationType = new NotificationType('json/schemaContent'); } +namespace ResultLimitReachedNotification { + export const type: NotificationType = new NotificationType('json/resultLimitReached'); +} + namespace ForceValidateRequest { export const type: RequestType = new RequestType('json/validate'); } @@ -211,7 +215,7 @@ namespace LimitExceededWarnings { } else { warning = { features: { [name]: name } }; warning.timeout = setTimeout(() => { - connection.window.showInformationMessage(`${posix.basename(uri)}: For performance reasons, ${Object.keys(warning.features).join(' and ')} have been limited to ${resultLimit} items.`); + connection.sendNotification(ResultLimitReachedNotification.type, `${posix.basename(uri)}: For performance reasons, ${Object.keys(warning.features).join(' and ')} have been limited to ${resultLimit} items.`); warning.timeout = undefined; }, 2000); pendingWarnings[uri] = warning; From 0f31ed29482b4032ea4e81ad4b6b4afd740ec692 Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 4 Dec 2019 16:09:43 +0100 Subject: [PATCH 156/255] fixes #85880 --- src/vs/workbench/contrib/files/browser/views/explorerView.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index dd98498ed43..022d76e86ad 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -297,6 +297,10 @@ export class ExplorerView extends ViewletPane { for (const stat of this.tree.getSelection()) { const controller = this.renderer.getCompressedNavigationController(stat); + if (controller && focusedStat && focusedStat !== stat && controller === this.compressedNavigationController) { + // This stat is compact with the focused one, it should not be part of the selection + continue; + } if (controller) { selectedStats.push(...controller.items); From 073bb36f406a981d8b2489d88fcde2446373851d Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 4 Dec 2019 16:25:36 +0100 Subject: [PATCH 157/255] fixes #83525 --- .../contrib/debug/browser/debugConfigurationManager.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index f67c70db9d8..ba79a6e94b6 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -496,10 +496,13 @@ abstract class AbstractLaunch { getConfigurationNames(includeCompounds = true): string[] { const config = this.getConfig(); - if (!config || !config.configurations || !Array.isArray(config.configurations)) { + if (!config || (!Array.isArray(config.configurations) && !Array.isArray(config.compounds))) { return []; } else { - const names = config.configurations.filter(cfg => cfg && typeof cfg.name === 'string').map(cfg => cfg.name); + const names: string[] = []; + if (config.configurations) { + names.push(...config.configurations.filter(cfg => cfg && typeof cfg.name === 'string').map(cfg => cfg.name)); + } if (includeCompounds && config.compounds) { if (config.compounds) { names.push(...config.compounds.filter(compound => typeof compound.name === 'string' && compound.configurations && compound.configurations.length) From 6d2eeee28aba86c329ac659acf8bfe2ab0390ab9 Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 4 Dec 2019 16:54:34 +0100 Subject: [PATCH 158/255] fixes #85938 --- src/vs/base/browser/ui/tree/abstractTree.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 5f40e3d8987..3699ee509d0 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -14,7 +14,7 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { ITreeModel, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeFilter, ITreeNavigator, ICollapseStateChangeEvent, ITreeDragAndDrop, TreeDragOverBubble, TreeVisibility, TreeFilterResult, ITreeModelSpliceEvent, TreeMouseEventTarget } from 'vs/base/browser/ui/tree/tree'; import { ISpliceable } from 'vs/base/common/sequence'; import { IDragAndDropData, StaticDND, DragAndDropData } from 'vs/base/browser/dnd'; -import { range, equals, distinctES6, fromSet } from 'vs/base/common/arrays'; +import { range, equals, distinctES6, fromSet, distinct } from 'vs/base/common/arrays'; import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; import { domEvent } from 'vs/base/browser/event'; import { fuzzyScore, FuzzyScore } from 'vs/base/common/filters'; @@ -997,7 +997,7 @@ class Trait { } private _set(nodes: ITreeNode[], silent: boolean, browserEvent?: UIEvent): void { - this.nodes = [...nodes]; + this.nodes = nodes.length > 1 && this.identityProvider ? distinct(nodes, node => this.identityProvider!.getId(node.element).toString()) : [...nodes]; this.elements = undefined; this._nodeSet = undefined; From a3846fb58d3ecb18df155b18624c9280c542fb33 Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 4 Dec 2019 17:07:21 +0100 Subject: [PATCH 159/255] explorer: not needed workaround --- src/vs/workbench/contrib/files/browser/views/explorerView.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 022d76e86ad..f09b093513a 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -297,11 +297,6 @@ export class ExplorerView extends ViewletPane { for (const stat of this.tree.getSelection()) { const controller = this.renderer.getCompressedNavigationController(stat); - if (controller && focusedStat && focusedStat !== stat && controller === this.compressedNavigationController) { - // This stat is compact with the focused one, it should not be part of the selection - continue; - } - if (controller) { selectedStats.push(...controller.items); } else { From 957b895664172a58b6728ea62a0594b622f60347 Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Wed, 4 Dec 2019 08:28:13 -0800 Subject: [PATCH 160/255] Fix #86050 --- src/vs/workbench/browser/media/style.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/workbench/browser/media/style.css b/src/vs/workbench/browser/media/style.css index f6c7bd7b620..bb6b38de289 100644 --- a/src/vs/workbench/browser/media/style.css +++ b/src/vs/workbench/browser/media/style.css @@ -180,6 +180,9 @@ body.web { .monaco-workbench select { -webkit-appearance: none; -moz-appearance: none; + /* Hides inner border from FF */ + border-block: none; + border-inline: none; } .monaco-workbench .select-container { From 429c2a58dcf73ba22052b3a34d43a746862cfe24 Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 4 Dec 2019 17:29:10 +0100 Subject: [PATCH 161/255] Revert "Remove incomplete checks (fixes #85844)" This reverts commit ef121d2d876e98114bf92062a779f3773dd916fc. --- src/vs/base/browser/ui/iconLabel/iconLabel.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/vs/base/browser/ui/iconLabel/iconLabel.ts b/src/vs/base/browser/ui/iconLabel/iconLabel.ts index 30b4246b9cf..425f657432c 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabel.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabel.ts @@ -163,11 +163,18 @@ export class IconLabel extends Disposable { class Label { + private label: string | string[] | undefined = undefined; private singleLabel: HTMLElement | undefined = undefined; constructor(private container: HTMLElement) { } setLabel(label: string | string[], options?: IIconLabelValueOptions): void { + if (this.label === label) { + return; + } + + this.label = label; + if (typeof label === 'string') { if (!this.singleLabel) { this.container.innerHTML = ''; @@ -217,11 +224,18 @@ function splitMatches(labels: string[], separator: string, matches: IMatch[] | u class LabelWithHighlights { + private label: string | string[] | undefined = undefined; private singleLabel: HighlightedLabel | undefined = undefined; constructor(private container: HTMLElement, private supportCodicons: boolean) { } setLabel(label: string | string[], options?: IIconLabelValueOptions): void { + if (this.label === label) { + return; + } + + this.label = label; + if (typeof label === 'string') { if (!this.singleLabel) { this.container.innerHTML = ''; From 85291a67f3cf9d89b602924c565a3e07a8aeff38 Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 4 Dec 2019 17:35:18 +0100 Subject: [PATCH 162/255] fixes #85844 --- src/vs/base/browser/ui/iconLabel/iconLabel.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/vs/base/browser/ui/iconLabel/iconLabel.ts b/src/vs/base/browser/ui/iconLabel/iconLabel.ts index 425f657432c..254aed56902 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabel.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabel.ts @@ -9,6 +9,7 @@ import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlighte import { IMatch } from 'vs/base/common/filters'; import { Disposable } from 'vs/base/common/lifecycle'; import { Range } from 'vs/base/common/range'; +import { equals } from 'vs/base/common/objects'; export interface IIconLabelCreationOptions { supportHighlights?: boolean; @@ -165,15 +166,17 @@ class Label { private label: string | string[] | undefined = undefined; private singleLabel: HTMLElement | undefined = undefined; + private options: IIconLabelValueOptions | undefined; constructor(private container: HTMLElement) { } setLabel(label: string | string[], options?: IIconLabelValueOptions): void { - if (this.label === label) { + if (this.label === label && equals(this.options, options)) { return; } this.label = label; + this.options = options; if (typeof label === 'string') { if (!this.singleLabel) { @@ -226,15 +229,17 @@ class LabelWithHighlights { private label: string | string[] | undefined = undefined; private singleLabel: HighlightedLabel | undefined = undefined; + private options: IIconLabelValueOptions | undefined; constructor(private container: HTMLElement, private supportCodicons: boolean) { } setLabel(label: string | string[], options?: IIconLabelValueOptions): void { - if (this.label === label) { + if (this.label === label && equals(this.options, options)) { return; } this.label = label; + this.options = options; if (typeof label === 'string') { if (!this.singleLabel) { From 721f2274694681dadd860dc2a6e5c986f869b6df Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 4 Dec 2019 18:02:42 +0100 Subject: [PATCH 163/255] fixes #86034 --- .../contrib/debug/browser/debugViewlet.ts | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts index 9a195bec864..d84b2cff137 100644 --- a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts @@ -15,7 +15,7 @@ import { StartDebugActionViewItem, FocusSessionActionViewItem } from 'vs/workben import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IProgressService } from 'vs/platform/progress/common/progress'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -119,22 +119,27 @@ export class DebugViewlet extends ViewContainerViewlet { if (CONTEXT_DEBUG_UX.getValue(this.contextKeyService) === 'simple') { return []; } - if (this.showInitialDebugActions) { - return [this.startAction, this.configureAction, this.toggleReplAction]; + if (!this.showInitialDebugActions) { + + if (!this.debugToolBarMenu) { + this.debugToolBarMenu = this.menuService.createMenu(MenuId.DebugToolBar, this.contextKeyService); + this._register(this.debugToolBarMenu); + } + + const { actions, disposable } = DebugToolBar.getActions(this.debugToolBarMenu, this.debugService, this.instantiationService); + if (this.disposeOnTitleUpdate) { + dispose(this.disposeOnTitleUpdate); + } + this.disposeOnTitleUpdate = disposable; + + return actions; } - if (!this.debugToolBarMenu) { - this.debugToolBarMenu = this.menuService.createMenu(MenuId.DebugToolBar, this.contextKeyService); - this._register(this.debugToolBarMenu); + if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) { + return [this.toggleReplAction]; } - const { actions, disposable } = DebugToolBar.getActions(this.debugToolBarMenu, this.debugService, this.instantiationService); - if (this.disposeOnTitleUpdate) { - dispose(this.disposeOnTitleUpdate); - } - this.disposeOnTitleUpdate = disposable; - - return actions; + return [this.startAction, this.configureAction, this.toggleReplAction]; } get showInitialDebugActions(): boolean { From 1a32116c63ecda2d72333bd8fc7de0e4dbcb0d6a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 4 Dec 2019 09:04:39 -0800 Subject: [PATCH 164/255] Make rendererType use markdown enum descriptions Fixes #86182 --- .../workbench/contrib/terminal/browser/terminal.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index 661e638a8e6..fa6e554525e 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -215,7 +215,7 @@ configurationRegistry.registerConfiguration({ 'terminal.integrated.rendererType': { type: 'string', enum: ['auto', 'canvas', 'dom', 'experimentalWebgl'], - enumDescriptions: [ + markdownEnumDescriptions: [ nls.localize('terminal.integrated.rendererType.auto', "Let VS Code guess which renderer to use."), nls.localize('terminal.integrated.rendererType.canvas', "Use the standard GPU/canvas-based renderer."), nls.localize('terminal.integrated.rendererType.dom', "Use the fallback DOM-based renderer."), From f3e74022beb260aef486f5c6cc7026a8de20354d Mon Sep 17 00:00:00 2001 From: Robert Jin Date: Wed, 4 Dec 2019 16:59:17 +0000 Subject: [PATCH 165/255] fix #85937: prevent duplicate selections --- src/vs/base/browser/ui/tree/abstractTree.ts | 2 +- src/vs/base/browser/ui/tree/asyncDataTree.ts | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 3699ee509d0..c7580993acb 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -997,7 +997,7 @@ class Trait { } private _set(nodes: ITreeNode[], silent: boolean, browserEvent?: UIEvent): void { - this.nodes = nodes.length > 1 && this.identityProvider ? distinct(nodes, node => this.identityProvider!.getId(node.element).toString()) : [...nodes]; + this.nodes = [...nodes]; this.elements = undefined; this._nodeSet = undefined; diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index c21a3a80964..91a1f9ddf9d 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -1189,14 +1189,16 @@ export class CompressibleAsyncDataTree extends As if (compressedNode) { for (let i = 0; i < compressedNode.elements.length; i++) { const id = getId(compressedNode.elements[i].element as T); + const element = compressedNode.elements[compressedNode.elements.length - 1].element as T; - if (oldSelection.has(id)) { - selection.push(compressedNode.elements[compressedNode.elements.length - 1].element as T); + // github.com/microsoft/vscode/issues/85938 + if (oldSelection.has(id) && selection.indexOf(element) === -1) { + selection.push(element); didChangeSelection = true; } - if (oldFocus.has(id)) { - focus.push(compressedNode.elements[compressedNode.elements.length - 1].element as T); + if (oldFocus.has(id) && focus.indexOf(element) === -1) { + focus.push(element); didChangeFocus = true; } } From f42ab17ac2a82172b17db5a4064888ee9304da73 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 4 Dec 2019 09:12:42 -0800 Subject: [PATCH 166/255] Use markdown in terminal contributions correctly Fixes #86287 --- .../contrib/terminal/browser/terminal.contribution.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index fa6e554525e..6fec1c4ede7 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -170,7 +170,7 @@ configurationRegistry.registerConfiguration({ default: DEFAULT_LINE_HEIGHT }, 'terminal.integrated.minimumContrastRatio': { - description: nls.localize('terminal.integrated.minimumContrastRatio', "When set the foreground color of each cell will change to try meet the contrast ratio specified. Example values:\n\n- 1: The default, do nothing.\n- 4.5: Minimum for WCAG AA compliance.\n- 7: Minimum for WCAG AAA compliance.\n- 21: White on black or black on white."), + markdownDescription: nls.localize('terminal.integrated.minimumContrastRatio', "When set the foreground color of each cell will change to try meet the contrast ratio specified. Example values:\n\n- 1: The default, do nothing.\n- 4.5: Minimum for WCAG AA compliance.\n- 7: Minimum for WCAG AAA compliance.\n- 21: White on black or black on white."), type: 'number', default: 1 }, @@ -205,7 +205,7 @@ configurationRegistry.registerConfiguration({ markdownDescription: nls.localize('terminal.integrated.detectLocale', "Controls whether to detect and set the `$LANG` environment variable to a UTF-8 compliant option since VS Code's terminal only supports UTF-8 encoded data coming from the shell."), type: 'string', enum: ['auto', 'off', 'on'], - enumDescriptions: [ + markdownEnumDescriptions: [ nls.localize('terminal.integrated.detectLocale.auto', "Set the `$LANG` environment variable if the existing variable does not exist or it does not end in `'.UTF-8'`."), nls.localize('terminal.integrated.detectLocale.off', "Do not set the `$LANG` environment variable."), nls.localize('terminal.integrated.detectLocale.on', "Always set the `$LANG` environment variable.") @@ -252,7 +252,7 @@ configurationRegistry.registerConfiguration({ default: false }, 'terminal.integrated.commandsToSkipShell': { - description: nls.localize('terminal.integrated.commandsToSkipShell', "A set of command IDs whose keybindings will not be sent to the shell and instead always be handled by Code. This allows the use of keybindings that would normally be consumed by the shell to act the same as when the terminal is not focused, for example ctrl+p to launch Quick Open.\nDefault Skipped Commands:\n\n{0}", DEFAULT_COMMANDS_TO_SKIP_SHELL.sort().map(command => `- ${command}`).join('\n')), + markdownDescription: nls.localize('terminal.integrated.commandsToSkipShell', "A set of command IDs whose keybindings will not be sent to the shell and instead always be handled by Code. This allows the use of keybindings that would normally be consumed by the shell to act the same as when the terminal is not focused, for example ctrl+p to launch Quick Open.\nDefault Skipped Commands:\n\n{0}", DEFAULT_COMMANDS_TO_SKIP_SHELL.sort().map(command => `- ${command}`).join('\n')), type: 'array', items: { type: 'string' @@ -260,7 +260,7 @@ configurationRegistry.registerConfiguration({ default: [] }, 'terminal.integrated.allowChords': { - markdownDescription: nls.localize('terminal.integrated.allowChords', "Whether or not to allow chord keybindings in the terminal. Note that when this is true and the keystroke results in a chord it will bypass `terminal.integrated.commandsToSkipShell`, setting this to false is particularly useful when you want ctrl+k to go to your shell (not VS Code)."), + markdownDescription: nls.localize('terminal.integrated.allowChords', "Whether or not to allow chord keybindings in the terminal. Note that when this is true and the keystroke results in a chord it will bypass `#terminal.integrated.commandsToSkipShell#`, setting this to false is particularly useful when you want ctrl+k to go to your shell (not VS Code)."), type: 'boolean', default: true }, From a56829c2258accf1767c9a1137fa2a5cd545fedf Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 4 Dec 2019 18:13:44 +0100 Subject: [PATCH 167/255] remove unused import --- src/vs/base/browser/ui/tree/abstractTree.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index c7580993acb..5f40e3d8987 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -14,7 +14,7 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { ITreeModel, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeFilter, ITreeNavigator, ICollapseStateChangeEvent, ITreeDragAndDrop, TreeDragOverBubble, TreeVisibility, TreeFilterResult, ITreeModelSpliceEvent, TreeMouseEventTarget } from 'vs/base/browser/ui/tree/tree'; import { ISpliceable } from 'vs/base/common/sequence'; import { IDragAndDropData, StaticDND, DragAndDropData } from 'vs/base/browser/dnd'; -import { range, equals, distinctES6, fromSet, distinct } from 'vs/base/common/arrays'; +import { range, equals, distinctES6, fromSet } from 'vs/base/common/arrays'; import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; import { domEvent } from 'vs/base/browser/event'; import { fuzzyScore, FuzzyScore } from 'vs/base/common/filters'; From 0fd472271d9c0cc8f923319418eaf7c559f9bee0 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Wed, 4 Dec 2019 10:02:23 -0800 Subject: [PATCH 168/255] Make all search-result editors have no line numbers And make that configurable --- extensions/search-result/package.json | 5 +++++ src/vs/workbench/contrib/search/browser/searchEditor.ts | 2 -- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/extensions/search-result/package.json b/extensions/search-result/package.json index 0a2af5e0a9d..5e7482b6033 100644 --- a/extensions/search-result/package.json +++ b/extensions/search-result/package.json @@ -19,6 +19,11 @@ "vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:search-result ./tsconfig.json" }, "contributes": { + "configurationDefaults": { + "[search-result]": { + "editor.lineNumbers": "off" + } + }, "commands": [ { "command": "searchResult.rerunSearch", diff --git a/src/vs/workbench/contrib/search/browser/searchEditor.ts b/src/vs/workbench/contrib/search/browser/searchEditor.ts index 46792e7b680..b2bc36fbd06 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/search/browser/searchEditor.ts @@ -291,8 +291,6 @@ export const createEditorFromSearchResult = const editor = await editorService.openEditor(possible); const control = editor?.getControl()!; - control.updateOptions({ lineNumbers: 'off' }); - const model = control.getModel() as ITextModel; model.deltaDecorations([], results.matchRanges.map(range => ({ range, options: { className: 'searchEditorFindMatch', stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges } }))); From 8ea7ce058e483c2bed0a6aac2a358368a3cf1b59 Mon Sep 17 00:00:00 2001 From: Rachel Macfarlane Date: Wed, 4 Dec 2019 10:19:04 -0800 Subject: [PATCH 169/255] Update high contrast scm diff decoration colors, fixes #86197 --- .../contrib/scm/browser/dirtydiffDecorator.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index a3cac4fbe01..4da76571bbd 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -809,37 +809,37 @@ export class DirtyDiffController extends Disposable implements IEditorContributi export const editorGutterModifiedBackground = registerColor('editorGutter.modifiedBackground', { dark: new Color(new RGBA(12, 125, 157)), light: new Color(new RGBA(102, 175, 224)), - hc: new Color(new RGBA(0, 73, 122)) + hc: new Color(new RGBA(0, 155, 249)) }, nls.localize('editorGutterModifiedBackground', "Editor gutter background color for lines that are modified.")); export const editorGutterAddedBackground = registerColor('editorGutter.addedBackground', { dark: new Color(new RGBA(88, 124, 12)), light: new Color(new RGBA(129, 184, 139)), - hc: new Color(new RGBA(27, 82, 37)) + hc: new Color(new RGBA(51, 171, 78)) }, nls.localize('editorGutterAddedBackground', "Editor gutter background color for lines that are added.")); export const editorGutterDeletedBackground = registerColor('editorGutter.deletedBackground', { dark: new Color(new RGBA(148, 21, 27)), light: new Color(new RGBA(202, 75, 81)), - hc: new Color(new RGBA(141, 14, 20)) + hc: new Color(new RGBA(252, 93, 109)) }, nls.localize('editorGutterDeletedBackground', "Editor gutter background color for lines that are deleted.")); export const minimapGutterModifiedBackground = registerColor('minimapGutter.modifiedBackground', { dark: new Color(new RGBA(12, 125, 157)), light: new Color(new RGBA(102, 175, 224)), - hc: new Color(new RGBA(0, 73, 122)) + hc: new Color(new RGBA(0, 155, 249)) }, nls.localize('minimapGutterModifiedBackground', "Minimap gutter background color for lines that are modified.")); export const minimapGutterAddedBackground = registerColor('minimapGutter.addedBackground', { dark: new Color(new RGBA(88, 124, 12)), light: new Color(new RGBA(129, 184, 139)), - hc: new Color(new RGBA(27, 82, 37)) + hc: new Color(new RGBA(51, 171, 78)) }, nls.localize('minimapGutterAddedBackground', "Minimap gutter background color for lines that are added.")); export const minimapGutterDeletedBackground = registerColor('minimapGutter.deletedBackground', { dark: new Color(new RGBA(148, 21, 27)), light: new Color(new RGBA(202, 75, 81)), - hc: new Color(new RGBA(141, 14, 20)) + hc: new Color(new RGBA(252, 93, 109)) }, nls.localize('minimapGutterDeletedBackground', "Minimap gutter background color for lines that are deleted.")); const overviewRulerDefault = new Color(new RGBA(0, 122, 204, 0.6)); From ed4d8133763c29fb41f9a6c04ed4e73d2b129db2 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Wed, 4 Dec 2019 10:21:07 -0800 Subject: [PATCH 170/255] Don't create duplicate identical search-editors. Fixes #86118 --- .../contrib/search/browser/searchEditor.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/searchEditor.ts b/src/vs/workbench/contrib/search/browser/searchEditor.ts index b2bc36fbd06..725ab6f93d0 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/search/browser/searchEditor.ts @@ -12,7 +12,7 @@ import { URI } from 'vs/base/common/uri'; import { ITextQuery, IPatternInfo, ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search'; import * as network from 'vs/base/common/network'; import { Range } from 'vs/editor/common/core/range'; -import { ITextModel, TrackedRangeStickiness } from 'vs/editor/common/model'; +import { ITextModel, TrackedRangeStickiness, EndOfLinePreference } from 'vs/editor/common/model'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder'; import { getOutOfWorkspaceEditorResources } from 'vs/workbench/contrib/search/common/search'; @@ -20,6 +20,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { searchEditorFindMatch, searchEditorFindMatchBorder } from 'vs/platform/theme/common/colorRegistry'; +import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; // Using \r\n on Windows inserts an extra newline between results. const lineDelimiter = '\n'; @@ -277,16 +278,25 @@ export const createEditorFromSearchResult = const labelFormatter = (uri: URI): string => labelService.getUriLabel(uri, { relative: true }); const results = serializeSearchResultForEditor(searchResult, rawIncludePattern, rawExcludePattern, 0, labelFormatter); - + const contents = results.text.join(lineDelimiter); let possible = { - contents: results.text.join(lineDelimiter), + contents, mode: 'search-result', resource: URI.from({ scheme: network.Schemas.untitled, path: searchTerm }) }; let id = 0; - while (editorService.getOpened(possible)) { + let existing = editorService.getOpened(possible); + while (existing) { + if (existing instanceof UntitledTextEditorInput) { + const model = await existing.resolve(); + const existingContents = model.textEditorModel.getValue(EndOfLinePreference.LF); + if (existingContents === contents) { + break; + } + } possible.resource = possible.resource.with({ path: searchTerm + '-' + ++id }); + existing = editorService.getOpened(possible); } const editor = await editorService.openEditor(possible); From c0bc9d8a2aed1c914bd99ae71d03a121eaa8ee68 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 4 Dec 2019 10:27:21 -0800 Subject: [PATCH 171/255] Add links to WCAG for minimumContrastRatio Fixes #86144 --- .../workbench/contrib/terminal/browser/terminal.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index 6fec1c4ede7..ba65ed8a973 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -170,7 +170,7 @@ configurationRegistry.registerConfiguration({ default: DEFAULT_LINE_HEIGHT }, 'terminal.integrated.minimumContrastRatio': { - markdownDescription: nls.localize('terminal.integrated.minimumContrastRatio', "When set the foreground color of each cell will change to try meet the contrast ratio specified. Example values:\n\n- 1: The default, do nothing.\n- 4.5: Minimum for WCAG AA compliance.\n- 7: Minimum for WCAG AAA compliance.\n- 21: White on black or black on white."), + markdownDescription: nls.localize('terminal.integrated.minimumContrastRatio', "When set the foreground color of each cell will change to try meet the contrast ratio specified. Example values:\n\n- 1: The default, do nothing.\n- 4.5: Minimum for [WCAG AA compliance](https://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-contrast.html).\n- 7: Minimum for [WCAG AAA compliance](https://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast7.html).\n- 21: White on black or black on white."), type: 'number', default: 1 }, From 43b0b9a7d570fd8ce2dbc6ee5c7bd6ec45b73c8e Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Wed, 4 Dec 2019 10:29:55 -0800 Subject: [PATCH 172/255] Update dropdown styles #86050 --- src/vs/workbench/browser/media/style.css | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/media/style.css b/src/vs/workbench/browser/media/style.css index bb6b38de289..b97ac21dda0 100644 --- a/src/vs/workbench/browser/media/style.css +++ b/src/vs/workbench/browser/media/style.css @@ -181,8 +181,7 @@ body.web { -webkit-appearance: none; -moz-appearance: none; /* Hides inner border from FF */ - border-block: none; - border-inline: none; + border: 1px solid; } .monaco-workbench .select-container { From 0034c13dc2988abaf2654965a74ace3ed1e87be0 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Wed, 4 Dec 2019 10:30:20 -0800 Subject: [PATCH 173/255] Clean up excluding code-search results. Fixes #86195. --- .../workbench/contrib/search/browser/search.contribution.ts | 2 +- src/vs/workbench/contrib/search/common/searchModel.ts | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index c487eddae0b..98a487a1695 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -695,7 +695,7 @@ configurationRegistry.registerConfiguration({ 'search.exclude': { type: 'object', markdownDescription: nls.localize('exclude', "Configure glob patterns for excluding files and folders in searches. Inherits all glob patterns from the `#files.exclude#` setting. Read more about glob patterns [here](https://code.visualstudio.com/docs/editor/codebasics#_advanced-search-options)."), - default: { '**/node_modules': true, '**/bower_components': true }, + default: { '**/node_modules': true, '**/bower_components': true, '**/*.code-search': true }, additionalProperties: { anyOf: [ { diff --git a/src/vs/workbench/contrib/search/common/searchModel.ts b/src/vs/workbench/contrib/search/common/searchModel.ts index 641a7557a37..99d8a29887d 100644 --- a/src/vs/workbench/contrib/search/common/searchModel.ts +++ b/src/vs/workbench/contrib/search/common/searchModel.ts @@ -982,11 +982,6 @@ export class SearchModel extends Disposable { search(query: ITextQuery, onProgress?: (result: ISearchProgressItem) => void): Promise { this.cancelSearch(); - // Exclude Search Editor results unless explicity included - const searchEditorFilenameGlob = `**/*.code-search`; - if (!query.includePattern || !query.includePattern[searchEditorFilenameGlob]) { - query.excludePattern = { ...(query.excludePattern ?? {}), [searchEditorFilenameGlob]: true }; - } this._searchQuery = query; if (!this.searchConfig.searchOnType) { From 9fd89ca3493e74dc659f4b5ab4319c10f15072e3 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 4 Dec 2019 10:24:56 -0800 Subject: [PATCH 174/255] Pick up TS 3.7.3 final --- extensions/package.json | 2 +- extensions/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/extensions/package.json b/extensions/package.json index d1030bbe6dd..65d512cb8a5 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -3,7 +3,7 @@ "version": "0.0.1", "description": "Dependencies shared by all extensions", "dependencies": { - "typescript": "3.7.3-insiders.20191123" + "typescript": "3.7.3" }, "scripts": { "postinstall": "node ./postinstall" diff --git a/extensions/yarn.lock b/extensions/yarn.lock index f2e7ae95079..a8eb51df657 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -2,7 +2,7 @@ # yarn lockfile v1 -typescript@3.7.3-insiders.20191123: - version "3.7.3-insiders.20191123" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.3-insiders.20191123.tgz#f3bef33a2a3f6e02f11bcc0c20b6f0de526f17fd" - integrity sha512-b+tLx4D0a6SeuaCa7iehdgkRKHsS67FkioQWw+0REjVNOYZ+AqJ0NjlnomK1hEUvSzSNrH9Du+m+Yiv7JlVpSg== +typescript@3.7.3: + version "3.7.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.3.tgz#b36840668a16458a7025b9eabfad11b66ab85c69" + integrity sha512-Mcr/Qk7hXqFBXMN7p7Lusj1ktCBydylfQM/FZCk5glCNQJrCUKPkMHdo9R0MTFWsC/4kPFvDS0fDPvukfCkFsw== From 57455124b52ebdf06d1b24fb8690a8f5a4d6f661 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 4 Dec 2019 10:57:19 -0800 Subject: [PATCH 175/255] Make sure we also log the typescript error properties on fatal error telemetry events Fixes #86205 We already log error metadata for failed requests. However we don't include this on the fatalError event. This makes investigation of these errors difficult --- .../src/tsServer/server.ts | 55 +++++++++++-------- .../src/tsServer/serverError.ts | 18 ++++++ .../src/typescriptServiceClient.ts | 19 ++++--- 3 files changed, 61 insertions(+), 31 deletions(-) diff --git a/extensions/typescript-language-features/src/tsServer/server.ts b/extensions/typescript-language-features/src/tsServer/server.ts index e4f9acd1975..782c1c56f05 100644 --- a/extensions/typescript-language-features/src/tsServer/server.ts +++ b/extensions/typescript-language-features/src/tsServer/server.ts @@ -60,7 +60,7 @@ export interface ITypeScriptServer { } export interface TsServerDelegate { - onFatalError(command: string): void; + onFatalError(command: string, error: Error): void; } export interface TsServerProcess { @@ -222,21 +222,13 @@ export class ProcessBasedTsServer extends Disposable implements ITypeScriptServe if (!executeInfo.token || !executeInfo.token.isCancellationRequested) { /* __GDPR__ "languageServiceErrorResponse" : { - "command" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "message" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" }, - "stack" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" }, - "errortext" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" }, "${include}": [ - "${TypeScriptCommonProperties}" + "${TypeScriptCommonProperties}", + "${TypeScriptRequestErrorProperties}" ] } */ - this._telemetryReporter.logTelemetry('languageServiceErrorResponse', { - command: err.serverCommand, - message: err.serverMessage || '', - stack: err.serverStack || '', - errortext: err.serverErrorText || '', - }); + this._telemetryReporter.logTelemetry('languageServiceErrorResponse', err.telemetry); } } @@ -367,9 +359,8 @@ export class SyntaxRoutingTsServer extends Disposable implements ITypeScriptServ } else if (SyntaxRoutingTsServer.sharedCommands.has(command)) { // Dispatch to both server but only return from syntax one - const enum RequestState { Unresolved, Resolved, Errored } - let syntaxRequestState = RequestState.Unresolved; - let semanticRequestState = RequestState.Unresolved; + let syntaxRequestState: RequestState.State = RequestState.Unresolved; + let semanticRequestState: RequestState.State = RequestState.Unresolved; // Also make sure we never cancel requests to just one server let token: vscode.CancellationToken | undefined = undefined; @@ -394,16 +385,16 @@ export class SyntaxRoutingTsServer extends Disposable implements ITypeScriptServ semanticRequest .then(result => { semanticRequestState = RequestState.Resolved; - if (syntaxRequestState === RequestState.Errored) { + if (syntaxRequestState.type === RequestState.Type.Errored) { // We've gone out of sync - this._delegate.onFatalError(command); + this._delegate.onFatalError(command, syntaxRequestState.err); } return result; }, err => { - semanticRequestState = RequestState.Errored; + semanticRequestState = new RequestState.Errored(err); if (syntaxRequestState === RequestState.Resolved) { // We've gone out of sync - this._delegate.onFatalError(command); + this._delegate.onFatalError(command, err); } throw err; }); @@ -413,16 +404,16 @@ export class SyntaxRoutingTsServer extends Disposable implements ITypeScriptServ syntaxRequest .then(result => { syntaxRequestState = RequestState.Resolved; - if (semanticRequestState === RequestState.Errored) { + if (semanticRequestState.type === RequestState.Type.Errored) { // We've gone out of sync - this._delegate.onFatalError(command); + this._delegate.onFatalError(command, semanticRequestState.err); } return result; }, err => { - syntaxRequestState = RequestState.Errored; + syntaxRequestState = new RequestState.Errored(err); if (semanticRequestState === RequestState.Resolved) { // We've gone out of sync - this._delegate.onFatalError(command); + this._delegate.onFatalError(command, err); } throw err; }); @@ -433,3 +424,21 @@ export class SyntaxRoutingTsServer extends Disposable implements ITypeScriptServ } } } + +namespace RequestState { + export const enum Type { Unresolved, Resolved, Errored } + + export const Unresolved = { type: Type.Unresolved } as const; + + export const Resolved = { type: Type.Resolved } as const; + + export class Errored { + readonly type = Type.Errored; + + constructor( + public readonly err: Error + ) { } + } + + export type State = typeof Unresolved | typeof Resolved | Errored; +} diff --git a/extensions/typescript-language-features/src/tsServer/serverError.ts b/extensions/typescript-language-features/src/tsServer/serverError.ts index 7f58aa6b0f5..5c844b5f4da 100644 --- a/extensions/typescript-language-features/src/tsServer/serverError.ts +++ b/extensions/typescript-language-features/src/tsServer/serverError.ts @@ -7,6 +7,7 @@ import * as Proto from '../protocol'; import { escapeRegExp } from '../utils/regexp'; import { TypeScriptVersion } from '../utils/versionProvider'; + export class TypeScriptServerError extends Error { public static create( serverId: string, @@ -31,6 +32,23 @@ export class TypeScriptServerError extends Error { public get serverCommand() { return this.response.command; } + public get telemetry() { + /* __GDPR__FRAGMENT__ + "TypeScriptRequestErrorProperties" : { + "command" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "message" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" }, + "stack" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" }, + "errortext" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" } + } + */ + return { + command: this.serverCommand, + message: this.serverMessage || '', + stack: this.serverStack || '', + errortext: this.serverErrorText || '', + } as const; + } + /** * Given a `errorText` from a tsserver request indicating failure in handling a request, * prepares a payload for telemetry-logging. diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index f45cb960dca..b3385c4c393 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -11,7 +11,9 @@ import BufferSyncSupport from './features/bufferSyncSupport'; import { DiagnosticKind, DiagnosticsManager } from './features/diagnostics'; import * as Proto from './protocol'; import { ITypeScriptServer } from './tsServer/server'; -import { ITypeScriptServiceClient, ServerResponse, TypeScriptRequests, ExecConfig } from './typescriptService'; +import { TypeScriptServerError } from './tsServer/serverError'; +import { TypeScriptServerSpawner } from './tsServer/spawner'; +import { ExecConfig, ITypeScriptServiceClient, ServerResponse, TypeScriptRequests } from './typescriptService'; import API from './utils/api'; import { TsServerLogLevel, TypeScriptServiceConfiguration } from './utils/configuration'; import { Disposable } from './utils/dispose'; @@ -25,7 +27,6 @@ import Tracer from './utils/tracer'; import { inferredProjectConfig } from './utils/tsconfig'; import { TypeScriptVersionPicker } from './utils/versionPicker'; import { TypeScriptVersion, TypeScriptVersionProvider } from './utils/versionProvider'; -import { TypeScriptServerSpawner } from './tsServer/spawner'; const localize = nls.loadMessageBundle(); @@ -314,7 +315,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType this.onDidChangeTypeScriptVersion(currentVersion); let mytoken = ++this.token; const handle = this.typescriptServerSpawner.spawn(currentVersion, this.configuration, this.pluginManager, { - onFatalError: (command) => this.fatalError(command), + onFatalError: (command, err) => this.fatalError(command, err), }); this.serverState = new ServerState.Running(handle, apiVersion, undefined, true); this.lastStart = Date.now(); @@ -670,7 +671,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType } if (config?.nonRecoverable) { - execution.catch(() => this.fatalError(command)); + execution.catch(err => this.fatalError(command, err)); } return execution; @@ -704,14 +705,16 @@ export default class TypeScriptServiceClient extends Disposable implements IType return this.bufferSyncSupport.interuptGetErr(f); } - private fatalError(command: string): void { + private fatalError(command: string, error: Error): void { /* __GDPR__ "fatalError" : { - "${include}": [ "${TypeScriptCommonProperties}" ], - "command" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + "${include}": [ + "${TypeScriptCommonProperties}", + "${TypeScriptRequestErrorProperties}" + ] } */ - this.logTelemetry('fatalError', { command }); + this.logTelemetry('fatalError', { command, ...(error instanceof TypeScriptServerError ? error.telemetry : {}) }); console.error(`A non-recoverable error occured while executing tsserver command: ${command}`); if (this.serverState.type === ServerState.Type.Running) { From 49ebe301861ef831bcc21581fabc747ec8eb88ac Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Wed, 4 Dec 2019 11:08:17 -0800 Subject: [PATCH 176/255] Fix #86294, update stack frame color token names --- .../debug/browser/debugCallStackContribution.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugCallStackContribution.ts b/src/vs/workbench/contrib/debug/browser/debugCallStackContribution.ts index 2959b8fd357..ee22aee0239 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCallStackContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCallStackContribution.ts @@ -212,17 +212,17 @@ registerThemingParticipant((theme, collector) => { `); } - const debugIconBreakpointStackframeColor = theme.getColor(debugIconBreakpointStackframeForeground); - if (debugIconBreakpointStackframeColor) { + const debugIconBreakpointCurrentStackframeForegroundColor = theme.getColor(debugIconBreakpointCurrentStackframeForeground); + if (debugIconBreakpointCurrentStackframeForegroundColor) { collector.addRule(` .monaco-workbench .codicon-debug-breakpoint-stackframe, .monaco-workbench .codicon-debug-breakpoint-stackframe-dot::after { - color: ${debugIconBreakpointStackframeColor} !important; + color: ${debugIconBreakpointCurrentStackframeForegroundColor} !important; } `); } - const debugIconBreakpointStackframeFocusedColor = theme.getColor(debugIconBreakpointStackframeFocusedForeground); + const debugIconBreakpointStackframeFocusedColor = theme.getColor(debugIconBreakpointStackframeForeground); if (debugIconBreakpointStackframeFocusedColor) { collector.addRule(` .monaco-workbench .codicon-debug-breakpoint-stackframe-focused { @@ -239,5 +239,5 @@ const focusedStackFrameColor = registerColor('editor.focusedStackFrameHighlightB const debugIconBreakpointForeground = registerColor('debugIcon.breakpointForeground', { dark: '#E51400', light: '#E51400', hc: '#E51400' }, localize('debugIcon.breakpointForeground', 'Icon color for breakpoints.')); const debugIconBreakpointDisabledForeground = registerColor('debugIcon.breakpointDisabledForeground', { dark: '#848484', light: '#848484', hc: '#848484' }, localize('debugIcon.breakpointDisabledForeground', 'Icon color for disabled breakpoints.')); const debugIconBreakpointUnverifiedForeground = registerColor('debugIcon.breakpointUnverifiedForeground', { dark: '#848484', light: '#848484', hc: '#848484' }, localize('debugIcon.breakpointUnverifiedForeground', 'Icon color for unverified breakpoints.')); -const debugIconBreakpointStackframeForeground = registerColor('debugIcon.breakpointStackframeForeground', { dark: '#FFCC00', light: '#FFCC00', hc: '#FFCC00' }, localize('debugIcon.breakpointStackframeForeground', 'Icon color for breakpoint stack frames.')); -const debugIconBreakpointStackframeFocusedForeground = registerColor('debugIcon.breakpointStackframeFocusedForeground', { dark: '#89D185', light: '#89D185', hc: '#89D185' }, localize('debugIcon.breakpointStackframeFocusedForeground', 'Icon color for breakpoint stack frames that are focused.')); +const debugIconBreakpointCurrentStackframeForeground = registerColor('debugIcon.breakpointCurrentStackframeForeground', { dark: '#FFCC00', light: '#FFCC00', hc: '#FFCC00' }, localize('debugIcon.breakpointCurrentStackframeForeground', 'Icon color for the current breakpoint stack frame.')); +const debugIconBreakpointStackframeForeground = registerColor('debugIcon.breakpointStackframeForeground', { dark: '#89D185', light: '#89D185', hc: '#89D185' }, localize('debugIcon.breakpointStackframeForeground', 'Icon color for all breakpoint stack frames.')); From 4bbb7aa8bd6fe09a240d652272633c7eacdd813f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 4 Dec 2019 11:15:42 -0800 Subject: [PATCH 177/255] xterm@4.3.0-beta.28.vscode.1 Diff: https://github.com/xtermjs/xterm.js/compare/397d053...8341c35 Fixes #86142 --- package.json | 2 +- remote/package.json | 2 +- remote/web/package.json | 2 +- remote/web/yarn.lock | 8 ++++---- remote/yarn.lock | 8 ++++---- yarn.lock | 8 ++++---- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 7fd8c125aaf..bf1a2181ead 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "vscode-ripgrep": "^1.5.7", "vscode-sqlite3": "4.0.9", "vscode-textmate": "4.4.0", - "xterm": "4.3.0-beta.28", + "xterm": "4.3.0-beta.28.vscode.1", "xterm-addon-search": "0.4.0-beta4", "xterm-addon-web-links": "0.2.1", "xterm-addon-webgl": "0.4.0-beta.11", diff --git a/remote/package.json b/remote/package.json index 216a7c37015..891ae131f63 100644 --- a/remote/package.json +++ b/remote/package.json @@ -20,7 +20,7 @@ "vscode-proxy-agent": "^0.5.2", "vscode-ripgrep": "^1.5.7", "vscode-textmate": "4.4.0", - "xterm": "4.3.0-beta.28", + "xterm": "4.3.0-beta.28.vscode.1", "xterm-addon-search": "0.4.0-beta4", "xterm-addon-web-links": "0.2.1", "xterm-addon-webgl": "0.4.0-beta.11", diff --git a/remote/web/package.json b/remote/web/package.json index 2a0519d282d..0aed404aaba 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -5,7 +5,7 @@ "onigasm-umd": "2.2.5", "semver-umd": "^5.5.3", "vscode-textmate": "4.4.0", - "xterm": "4.3.0-beta.28", + "xterm": "4.3.0-beta.28.vscode.1", "xterm-addon-search": "0.4.0-beta4", "xterm-addon-web-links": "0.2.1", "xterm-addon-webgl": "0.4.0-beta.11" diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index 1e2d3f0de21..18078da8d19 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -46,7 +46,7 @@ xterm-addon-webgl@0.4.0-beta.11: resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.4.0-beta.11.tgz#0e4a7242e2353cf74aba55e5a5bdc0b4ec87ad10" integrity sha512-AteDxm1RFy1WnjY9r5iJSETozLebvUkR+jextdZk/ASsK21vsYK0DuVWwRI8afgiN2hUVhxcxuHEJUOV+CJDQA== -xterm@4.3.0-beta.28: - version "4.3.0-beta.28" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.3.0-beta.28.tgz#80f7c4ba8f6ee3c953e6f33f8ce5aef08d5a8354" - integrity sha512-WWZ4XCvce5h+klL6ObwtMauJff/n2KGGOwJJkDbJhrAjVy2a77GKgAedJTDDFGgKJ6ix1d7puHtVSSKflIVaDQ== +xterm@4.3.0-beta.28.vscode.1: + version "4.3.0-beta.28.vscode.1" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.3.0-beta.28.vscode.1.tgz#89b85398b5801708e833d08bf92b2124fa128943" + integrity sha512-JNHNZyDtAWnybJTrenPzD6g/yXpHOvPqmjau91Up4onRbjQYMSNlth17SqaES68DKn/+4kcIl2c/RG5SXJjvGw== diff --git a/remote/yarn.lock b/remote/yarn.lock index 6b661b17e47..a45c4d94ff5 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -433,10 +433,10 @@ xterm-addon-webgl@0.4.0-beta.11: resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.4.0-beta.11.tgz#0e4a7242e2353cf74aba55e5a5bdc0b4ec87ad10" integrity sha512-AteDxm1RFy1WnjY9r5iJSETozLebvUkR+jextdZk/ASsK21vsYK0DuVWwRI8afgiN2hUVhxcxuHEJUOV+CJDQA== -xterm@4.3.0-beta.28: - version "4.3.0-beta.28" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.3.0-beta.28.tgz#80f7c4ba8f6ee3c953e6f33f8ce5aef08d5a8354" - integrity sha512-WWZ4XCvce5h+klL6ObwtMauJff/n2KGGOwJJkDbJhrAjVy2a77GKgAedJTDDFGgKJ6ix1d7puHtVSSKflIVaDQ== +xterm@4.3.0-beta.28.vscode.1: + version "4.3.0-beta.28.vscode.1" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.3.0-beta.28.vscode.1.tgz#89b85398b5801708e833d08bf92b2124fa128943" + integrity sha512-JNHNZyDtAWnybJTrenPzD6g/yXpHOvPqmjau91Up4onRbjQYMSNlth17SqaES68DKn/+4kcIl2c/RG5SXJjvGw== yauzl@^2.9.2: version "2.10.0" diff --git a/yarn.lock b/yarn.lock index a4b936375f7..edbbc9743d0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9374,10 +9374,10 @@ xterm-addon-webgl@0.4.0-beta.11: resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.4.0-beta.11.tgz#0e4a7242e2353cf74aba55e5a5bdc0b4ec87ad10" integrity sha512-AteDxm1RFy1WnjY9r5iJSETozLebvUkR+jextdZk/ASsK21vsYK0DuVWwRI8afgiN2hUVhxcxuHEJUOV+CJDQA== -xterm@4.3.0-beta.28: - version "4.3.0-beta.28" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.3.0-beta.28.tgz#80f7c4ba8f6ee3c953e6f33f8ce5aef08d5a8354" - integrity sha512-WWZ4XCvce5h+klL6ObwtMauJff/n2KGGOwJJkDbJhrAjVy2a77GKgAedJTDDFGgKJ6ix1d7puHtVSSKflIVaDQ== +xterm@4.3.0-beta.28.vscode.1: + version "4.3.0-beta.28.vscode.1" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.3.0-beta.28.vscode.1.tgz#89b85398b5801708e833d08bf92b2124fa128943" + integrity sha512-JNHNZyDtAWnybJTrenPzD6g/yXpHOvPqmjau91Up4onRbjQYMSNlth17SqaES68DKn/+4kcIl2c/RG5SXJjvGw== y18n@^3.2.1: version "3.2.1" From 44a6a7645685f02f507466c2e7c7a8f6ec1a7db9 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Wed, 4 Dec 2019 11:17:07 -0800 Subject: [PATCH 178/255] Fix search editor query string unescaping (Fixes #86203) --- .../contrib/search/browser/searchEditor.ts | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/search/browser/searchEditor.ts b/src/vs/workbench/contrib/search/browser/searchEditor.ts index 725ab6f93d0..46d8c80250b 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/search/browser/searchEditor.ts @@ -21,6 +21,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { searchEditorFindMatch, searchEditorFindMatchBorder } from 'vs/platform/theme/common/colorRegistry'; import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; +import { localize } from 'vs/nls'; // Using \r\n on Windows inserts an extra newline between results. const lineDelimiter = '\n'; @@ -173,7 +174,28 @@ const searchHeaderToContentPattern = (header: string[]): SearchHeader => { context: undefined }; - const unescapeNewlines = (str: string) => str.replace(/\\\\/g, '\\').replace(/\\n/g, '\n'); + const unescapeNewlines = (str: string) => { + let out = ''; + for (let i = 0; i < str.length; i++) { + if (str[i] === '\\') { + i++; + const escaped = str[i]; + + if (escaped === 'n') { + out += '\n'; + } + else if (escaped === '\\') { + out += '\\'; + } + else { + throw Error(localize('invalidQueryStringError', "All backslashes in Query string must be escaped (\\\\)")); + } + } else { + out += str[i]; + } + } + return out; + }; const parseYML = /^# ([^:]*): (.*)$/; for (const line of header) { const parsed = parseYML.exec(line); From bb46c283287bd6b5e3417478ea5664e4993e6b0d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 4 Dec 2019 11:19:46 -0800 Subject: [PATCH 179/255] Update distro --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bf1a2181ead..751de76f58a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.41.0", - "distro": "de758a726231fcf8e04422e65e29cd792f21c6c4", + "distro": "17f1b806c349d58f96b4aef97ae59d836e2c5605", "author": { "name": "Microsoft Corporation" }, From 187c067b276b317704e458a1fae3636fadbd05d7 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Wed, 4 Dec 2019 11:29:32 -0800 Subject: [PATCH 180/255] Fix typo in command name --- extensions/search-result/package.nls.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/search-result/package.nls.json b/extensions/search-result/package.nls.json index a4b0fb83845..ce90d23c09c 100644 --- a/extensions/search-result/package.nls.json +++ b/extensions/search-result/package.nls.json @@ -2,5 +2,5 @@ "displayName": "Search Result", "description": "Provides syntax highlighting and language features for tabbed search results.", "searchResult.rerunSearch.title": "Search Again", - "searchResult.rerunSearchWithContext.title": "Search Again (Wth Context)" + "searchResult.rerunSearchWithContext.title": "Search Again (With Context)" } From 520c9998c3c3b8ffeee697f3b0b982c7826b0cb2 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Wed, 4 Dec 2019 11:41:57 -0800 Subject: [PATCH 181/255] Increase search input history delay, and skip delay on enter. Fixes #86288. --- src/vs/workbench/contrib/search/browser/searchView.ts | 2 +- src/vs/workbench/contrib/search/browser/searchWidget.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 419611a9462..2d97255d2d6 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -206,7 +206,7 @@ export class SearchView extends ViewletPane { this.delayedRefresh = this._register(new Delayer(250)); - this.addToSearchHistoryDelayer = this._register(new Delayer(500)); + this.addToSearchHistoryDelayer = this._register(new Delayer(2000)); this.actions = [ this._register(this.instantiationService.createInstance(ClearSearchResultsAction, ClearSearchResultsAction.ID, ClearSearchResultsAction.LABEL)), diff --git a/src/vs/workbench/contrib/search/browser/searchWidget.ts b/src/vs/workbench/contrib/search/browser/searchWidget.ts index a7ebbd9cb19..3c9a7a6b1fd 100644 --- a/src/vs/workbench/contrib/search/browser/searchWidget.ts +++ b/src/vs/workbench/contrib/search/browser/searchWidget.ts @@ -466,6 +466,7 @@ export class SearchWidget extends Widget { } if (keyboardEvent.equals(KeyCode.Enter)) { + this.searchInput.onSearchSubmit(); this.submitSearch(); keyboardEvent.preventDefault(); } From 72779d523412a6df73d585efdb2f903c340b56ca Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Wed, 4 Dec 2019 11:53:55 -0800 Subject: [PATCH 182/255] Fix "Cannot create regex from empty string" in search editor. Closes #86200. --- src/vs/workbench/contrib/search/browser/searchEditor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/search/browser/searchEditor.ts b/src/vs/workbench/contrib/search/browser/searchEditor.ts index 46d8c80250b..9b54955af67 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/search/browser/searchEditor.ts @@ -239,7 +239,7 @@ export const refreshActiveEditorSearch = const textModel = model as ITextModel; - const header = textModel.getValueInRange(new Range(1, 1, 5, 1)) + const header = textModel.getValueInRange(new Range(1, 1, 5, 1), EndOfLinePreference.LF) .split(lineDelimiter) .filter(line => line.indexOf('# ') === 0); From 03a567127f119a17ee83d80c45c2083b764f2fc6 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Wed, 4 Dec 2019 14:13:04 -0800 Subject: [PATCH 183/255] [Search] Fix focus sometimes going to wrong element. Fixes #86234 --- src/vs/workbench/contrib/search/browser/searchView.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 2d97255d2d6..d11471d7ff7 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -963,13 +963,15 @@ export class SearchView extends ViewletPane { return this.searchWidget && this.searchWidget.searchInput.getValue().length > 0; } - clearSearchResults(): void { + clearSearchResults(clearInput = true): void { this.viewModel.searchResult.clear(); this.showEmptyStage(true); if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) { this.showSearchWithoutFolderMessage(); } - this.searchWidget.clear(); + if (clearInput) { + this.searchWidget.clear(); + } this.viewModel.cancelSearch(); this.updateActions(); @@ -1202,7 +1204,7 @@ export class SearchView extends ViewletPane { const useExcludesAndIgnoreFiles = this.inputPatternExcludes.useExcludesAndIgnoreFiles(); if (contentPattern.length === 0) { - this.clearSearchResults(); + this.clearSearchResults(false); this.clearMessage(); return; } From 73c788749636c6278898d6f2774c6e9449de1cf2 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Wed, 4 Dec 2019 15:35:19 -0800 Subject: [PATCH 184/255] [Search Editor] Make clicking take you to the location you clicked. Ref #86190. --- extensions/search-result/src/extension.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/extensions/search-result/src/extension.ts b/extensions/search-result/src/extension.ts index c88a4feab63..6956a63d329 100644 --- a/extensions/search-result/src/extension.ts +++ b/extensions/search-result/src/extension.ts @@ -7,7 +7,7 @@ import * as vscode from 'vscode'; import * as pathUtils from 'path'; const FILE_LINE_REGEX = /^(\S.*):$/; -const RESULT_LINE_REGEX = /^(\s+)(\d+)(?::| )(\s+)(.*)$/; +const RESULT_LINE_REGEX = /^(\s+)(\d+)(:| )(\s+)(.*)$/; const SEARCH_RESULT_SELECTOR = { language: 'search-result' }; const DIRECTIVES = ['# Query:', '# Flags:', '# Including:', '# Excluding:', '# ContextLines:']; const FLAGS = ['RegExp', 'CaseSensitive', 'IgnoreExcludeSettings', 'WordMatch']; @@ -66,7 +66,13 @@ export function activate(context: vscode.ExtensionContext) { return []; } - return [lineResult.location]; + const translateRangeSidewaysBy = (r: vscode.Range, n: number) => + r.with({ start: new vscode.Position(r.start.line, Math.max(0, n - r.start.character)), end: new vscode.Position(r.end.line, Math.max(0, n - r.end.character)) }); + + return [{ + ...lineResult.location, + targetSelectionRange: translateRangeSidewaysBy(lineResult.location.targetSelectionRange!, position.character - 1) + }]; } }), @@ -166,13 +172,14 @@ function parseSearchResults(document: vscode.TextDocument, token: vscode.Cancell const resultLine = RESULT_LINE_REGEX.exec(line); if (resultLine) { - const [, indentation, _lineNumber, resultIndentation] = resultLine; + const [, indentation, _lineNumber, seperator, resultIndentation] = resultLine; const lineNumber = +_lineNumber - 1; - const resultStart = (indentation + _lineNumber + ':' + resultIndentation).length; + const resultStart = (indentation + _lineNumber + seperator + resultIndentation).length; + const metadataOffset = (indentation + _lineNumber + seperator).length; const location: vscode.LocationLink = { targetRange: new vscode.Range(Math.max(lineNumber - 3, 0), 0, lineNumber + 3, line.length), - targetSelectionRange: new vscode.Range(lineNumber, 0, lineNumber, line.length), + targetSelectionRange: new vscode.Range(lineNumber, metadataOffset, lineNumber, metadataOffset), targetUri: currentTarget, originSelectionRange: new vscode.Range(i, resultStart, i, line.length), }; From 5a84679fd7aca1156f800c76e8b884fe566353ad Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 4 Dec 2019 15:19:00 -0800 Subject: [PATCH 185/255] Remove tga from documented supported image format list For #84533 --- extensions/image-preview/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/extensions/image-preview/README.md b/extensions/image-preview/README.md index ccb5ac3954a..d3f0bd6cb6c 100644 --- a/extensions/image-preview/README.md +++ b/extensions/image-preview/README.md @@ -13,5 +13,4 @@ Supported image formats: - `*.bmp` - `*.gif` - `*.ico` -- `*.tga` - `*.webp` From bc68bd20916de9c3ad196a0e8c8731550bdac13f Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 4 Dec 2019 15:35:53 -0800 Subject: [PATCH 186/255] Use monospace font for overloads From #84682 This prevents slight shifting of the parameter hints box while going through the overloads --- src/vs/editor/contrib/parameterHints/parameterHints.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/editor/contrib/parameterHints/parameterHints.css b/src/vs/editor/contrib/parameterHints/parameterHints.css index e77cb357003..83e1d3bcb3a 100644 --- a/src/vs/editor/contrib/parameterHints/parameterHints.css +++ b/src/vs/editor/contrib/parameterHints/parameterHints.css @@ -95,6 +95,7 @@ height: 12px; line-height: 12px; opacity: 0.5; + font-family: var(--monaco-monospace-font); } .monaco-editor .parameter-hints-widget .signature .parameter.active { From a07b2fbef408f2f1ee7810e80d02f10373ed4c3b Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 4 Dec 2019 15:36:40 -0800 Subject: [PATCH 187/255] Fix parameter hints overload border not taking up entire height For #84682 --- src/vs/editor/contrib/parameterHints/parameterHints.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/editor/contrib/parameterHints/parameterHints.css b/src/vs/editor/contrib/parameterHints/parameterHints.css index 83e1d3bcb3a..dde72560cc5 100644 --- a/src/vs/editor/contrib/parameterHints/parameterHints.css +++ b/src/vs/editor/contrib/parameterHints/parameterHints.css @@ -34,6 +34,7 @@ .monaco-editor .parameter-hints-widget .body { display: flex; flex-direction: column; + min-height: 100%; } .monaco-editor .parameter-hints-widget .signature { From 02a1ddcf96a7a1f4eb88ebc3524138fed89f7b9d Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 4 Dec 2019 15:37:41 -0800 Subject: [PATCH 188/255] Add slight padding to overload count Fixes #86334 --- src/vs/editor/contrib/parameterHints/parameterHints.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/editor/contrib/parameterHints/parameterHints.css b/src/vs/editor/contrib/parameterHints/parameterHints.css index dde72560cc5..c764f5e9581 100644 --- a/src/vs/editor/contrib/parameterHints/parameterHints.css +++ b/src/vs/editor/contrib/parameterHints/parameterHints.css @@ -73,6 +73,7 @@ .monaco-editor .parameter-hints-widget.multiple .controls { display: flex; + padding: 0 2px; } .monaco-editor .parameter-hints-widget.multiple .button { From 401c633b4d92f827efc810eff6a1924fdcf3c60b Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Wed, 4 Dec 2019 15:59:40 -0800 Subject: [PATCH 189/255] [Search on Type] Dont trigger search on initalizatioon. Fixes #86129. --- .../contrib/search/browser/patternInputWidget.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/patternInputWidget.ts b/src/vs/workbench/contrib/search/browser/patternInputWidget.ts index 0d41239f527..b5502c58cf9 100644 --- a/src/vs/workbench/contrib/search/browser/patternInputWidget.ts +++ b/src/vs/workbench/contrib/search/browser/patternInputWidget.ts @@ -149,12 +149,6 @@ export class PatternInputWidget extends Widget { this._register(attachInputBoxStyler(this.inputBox, this.themeService)); this.inputFocusTracker = dom.trackFocus(this.inputBox.inputElement); this.onkeyup(this.inputBox.inputElement, (keyboardEvent) => this.onInputKeyUp(keyboardEvent)); - this._register(this.inputBox.onDidChange(() => { - if (this.searchConfig.searchOnType) { - this._onCancel.fire(); - this.searchOnTypeDelayer.trigger(() => this._onSubmit.fire(true), this.searchConfig.searchOnTypeDebouncePeriod); - } - })); const controls = document.createElement('div'); controls.className = 'controls'; @@ -176,6 +170,10 @@ export class PatternInputWidget extends Widget { this._onCancel.fire(); return; default: + if (this.searchConfig.searchOnType) { + this._onCancel.fire(); + this.searchOnTypeDelayer.trigger(() => this._onSubmit.fire(true), this.searchConfig.searchOnTypeDebouncePeriod); + } return; } } From d3cda21f0e7e3187d53b4db9c52465f1cc660d74 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Wed, 4 Dec 2019 16:11:32 -0800 Subject: [PATCH 190/255] Make default search editor result highlight a bit less transparent. Closes #86185. --- src/vs/platform/theme/common/colorRegistry.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index 43163b757bc..1fb0eb51c41 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -304,8 +304,8 @@ export const editorFindRangeHighlightBorder = registerColor('editor.findRangeHig * * Distinct from normal editor find match to allow for better differentiation */ -export const searchEditorFindMatch = registerColor('searchEditor.findMatchBackground', { light: transparent(editorFindMatchHighlight, 0.5), dark: transparent(editorFindMatchHighlight, 0.5), hc: editorFindMatchHighlight }, nls.localize('searchEditor.queryMatch', "Color of the Search Editor query matches.")); -export const searchEditorFindMatchBorder = registerColor('searchEditor.findMatchBorder', { light: transparent(editorFindMatchHighlightBorder, 0.5), dark: transparent(editorFindMatchHighlightBorder, 0.5), hc: editorFindMatchHighlightBorder }, nls.localize('searchEditor.editorFindMatchBorder', "Border color of the Search Editor query matches.")); +export const searchEditorFindMatch = registerColor('searchEditor.findMatchBackground', { light: transparent(editorFindMatchHighlight, 0.66), dark: transparent(editorFindMatchHighlight, 0.66), hc: editorFindMatchHighlight }, nls.localize('searchEditor.queryMatch', "Color of the Search Editor query matches.")); +export const searchEditorFindMatchBorder = registerColor('searchEditor.findMatchBorder', { light: transparent(editorFindMatchHighlightBorder, 0.66), dark: transparent(editorFindMatchHighlightBorder, 0.66), hc: editorFindMatchHighlightBorder }, nls.localize('searchEditor.editorFindMatchBorder', "Border color of the Search Editor query matches.")); /** * Editor hover From 88e80b9a18b2cc3de0ca3a02977c3e2d4b088a60 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 4 Dec 2019 16:05:33 -0800 Subject: [PATCH 191/255] debug: restart as extension host if any parent is an extension host --- .../contrib/debug/browser/debugService.ts | 12 +++++---- .../contrib/debug/common/debugUtils.ts | 27 ++++++++++++------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 24dc3d59a8c..81841ebec67 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -41,7 +41,7 @@ import { deepClone, equals } from 'vs/base/common/objects'; import { DebugSession } from 'vs/workbench/contrib/debug/browser/debugSession'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { IDebugService, State, IDebugSession, CONTEXT_DEBUG_TYPE, CONTEXT_DEBUG_STATE, CONTEXT_IN_DEBUG_MODE, IThread, IDebugConfiguration, VIEWLET_ID, REPL_ID, IConfig, ILaunch, IViewModel, IConfigurationManager, IDebugModel, IEnablement, IBreakpoint, IBreakpointData, ICompound, IGlobalConfig, IStackFrame, AdapterEndEvent, getStateLabel, IDebugSessionOptions, CONTEXT_DEBUG_UX } from 'vs/workbench/contrib/debug/common/debug'; -import { isExtensionHostDebugging } from 'vs/workbench/contrib/debug/common/debugUtils'; +import { getExtensionHostDebugSession } from 'vs/workbench/contrib/debug/common/debugUtils'; import { isErrorWithActions, createErrorWithActions } from 'vs/base/common/errorsWithActions'; import { RunOnceScheduler } from 'vs/base/common/async'; import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; @@ -539,8 +539,9 @@ export class DebugService implements IDebugService { } // 'Run without debugging' mode VSCode must terminate the extension host. More details: #3905 - if (isExtensionHostDebugging(session.configuration) && session.state === State.Running && session.configuration.noDebug) { - this.extensionHostDebugService.close(session.getId()); + const extensionDebugSession = getExtensionHostDebugSession(session); + if (extensionDebugSession && extensionDebugSession.state === State.Running && extensionDebugSession.configuration.noDebug) { + this.extensionHostDebugService.close(extensionDebugSession.getId()); } this.telemetryDebugSessionStop(session, adapterExitEvent); @@ -590,10 +591,11 @@ export class DebugService implements IDebugService { return this.runTaskAndCheckErrors(session.root, session.configuration.preLaunchTask); }; - if (isExtensionHostDebugging(session.configuration)) { + const extensionDebugSession = getExtensionHostDebugSession(session); + if (extensionDebugSession) { const taskResult = await runTasks(); if (taskResult === TaskRunResult.Success) { - this.extensionHostDebugService.reload(session.getId()); + this.extensionHostDebugService.reload(extensionDebugSession.getId()); } return; diff --git a/src/vs/workbench/contrib/debug/common/debugUtils.ts b/src/vs/workbench/contrib/debug/common/debugUtils.ts index 38aebf4b083..89c02df8bb7 100644 --- a/src/vs/workbench/contrib/debug/common/debugUtils.ts +++ b/src/vs/workbench/contrib/debug/common/debugUtils.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { equalsIgnoreCase } from 'vs/base/common/strings'; -import { IConfig, IDebuggerContribution, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebuggerContribution, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; import { URI as uri } from 'vs/base/common/uri'; import { isAbsolute } from 'vs/base/common/path'; import { deepClone } from 'vs/base/common/objects'; @@ -24,19 +24,28 @@ export function formatPII(value: string, excludePII: boolean, args: { [key: stri } export function isSessionAttach(session: IDebugSession): boolean { - return !session.parentSession && session.configuration.request === 'attach' && !isExtensionHostDebugging(session.configuration); + return !session.parentSession && session.configuration.request === 'attach' && !getExtensionHostDebugSession(session); } -export function isExtensionHostDebugging(config: IConfig) { - if (!config.type) { - return false; +/** + * Returns the session or any parent which is an extension host debug session. + * Returns undefined if there's none. + */ +export function getExtensionHostDebugSession(session: IDebugSession): IDebugSession | void { + let type = session.configuration.type; + if (!type) { + return; } - const type = config.type === 'vslsShare' - ? (config).adapterProxy.configuration.type - : config.type; + if (type === 'vslsShare') { + type = (session.configuration).adapterProxy.configuration.type; + } - return equalsIgnoreCase(type, 'extensionhost') || equalsIgnoreCase(type, 'pwa-extensionhost'); + if (equalsIgnoreCase(type, 'extensionhost') || equalsIgnoreCase(type, 'pwa-extensionhost')) { + return session; + } + + return session.parentSession ? getExtensionHostDebugSession(session.parentSession) : undefined; } // only a debugger contributions with a label, program, or runtime attribute is considered a "defining" or "main" debugger contribution From beb8a0c7b7a1a383181c7bab6f99f007e0629d43 Mon Sep 17 00:00:00 2001 From: rebornix Date: Wed, 4 Dec 2019 16:26:41 -0800 Subject: [PATCH 192/255] Fix #86347. Fixed overflow widget only for extension viewlet --- .../browser/suggestEnabledInput/suggestEnabledInput.ts | 3 ++- .../workbench/contrib/extensions/browser/extensionsViewlet.ts | 2 +- .../workbench/contrib/preferences/browser/settingsEditor2.ts | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts index eb0499385ca..ee09a61a8e6 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts @@ -72,6 +72,7 @@ interface SuggestEnabledInputOptions { * Context key tracking the focus state of this element */ focusContextKey?: IContextKey; + fixedOverflowWidgets?: boolean; } export interface ISuggestEnabledInputStyleOverrides extends IStyleOverrides { @@ -126,7 +127,7 @@ export class SuggestEnabledInput extends Widget implements IThemable { this.placeholderText = append(this.stylingContainer, $('.suggest-input-placeholder', undefined, options.placeholderText || '')); const editorOptions: IEditorOptions = mixin( - getSimpleEditorOptions(false), + getSimpleEditorOptions(!!options.fixedOverflowWidgets), getSuggestEnabledInputOptions(ariaLabel)); this.inputWidget = instantiationService.createInstance(CodeEditorWidget, this.stylingContainer, diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index 4ac7d34337e..669297557d5 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -405,7 +405,7 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio else { return 'd'; } }, provideResults: (query: string) => Query.suggestions(query) - }, placeholder, 'extensions:searchinput', { placeholderText: placeholder, value: searchValue })); + }, placeholder, 'extensions:searchinput', { placeholderText: placeholder, value: searchValue, fixedOverflowWidgets: false })); if (this.searchBox.getValue()) { this.triggerSearch(); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index 09467700dd5..e339ce2784e 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -406,6 +406,7 @@ export class SettingsEditor2 extends BaseEditor { }, searchBoxLabel, 'settingseditor:searchinput' + SettingsEditor2.NUM_INSTANCES++, { placeholderText: searchBoxLabel, focusContextKey: this.searchFocusContextKey, + fixedOverflowWidgets: true // TODO: Aria-live }) ); From e904ef98a86987d39d710230c9359dd1d06611df Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Wed, 4 Dec 2019 16:41:54 -0800 Subject: [PATCH 193/255] fix empty group issue refs #85440 --- src/vs/editor/contrib/contextmenu/contextmenu.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/contextmenu/contextmenu.ts b/src/vs/editor/contrib/contextmenu/contextmenu.ts index 6a301ba93e4..6114c170cce 100644 --- a/src/vs/editor/contrib/contextmenu/contextmenu.ts +++ b/src/vs/editor/contrib/contextmenu/contextmenu.ts @@ -148,19 +148,29 @@ export class ContextMenuController implements IEditorContribution { // translate them into other actions for (let group of groups) { const [, actions] = group; + let addedItems = 0; for (const action of actions) { if (action instanceof SubmenuItemAction) { const subActions = this._getMenuActions(model, action.item.submenu); if (subActions.length > 0) { result.push(new ContextSubMenu(action.label, subActions)); + addedItems++; } } else { result.push(action); + addedItems++; } } - result.push(new Separator()); + + if (addedItems) { + result.push(new Separator()); + } } - result.pop(); // remove last separator + + if (result.length) { + result.pop(); // remove last separator + } + return result; } From 52de8700e7446ed6ac88e91c0d20c549af23f711 Mon Sep 17 00:00:00 2001 From: Pine Wu Date: Wed, 4 Dec 2019 16:51:41 -0800 Subject: [PATCH 194/255] Fix #86211 --- extensions/html-language-features/client/src/htmlMain.ts | 2 +- extensions/html-language-features/package.json | 4 ++-- extensions/html-language-features/package.nls.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/extensions/html-language-features/client/src/htmlMain.ts b/extensions/html-language-features/client/src/htmlMain.ts index 4d900214bd1..9a4d392ff28 100644 --- a/extensions/html-language-features/client/src/htmlMain.ts +++ b/extensions/html-language-features/client/src/htmlMain.ts @@ -118,7 +118,7 @@ export function activate(context: ExtensionContext) { return client.sendRequest(MatchingTagPositionRequest.type, param); }; - disposable = activateMatchingTagSelection(matchingTagPositionRequestor, { html: true, handlebars: true }, 'html.autoSelectingMatchingTags'); + disposable = activateMatchingTagSelection(matchingTagPositionRequestor, { html: true, handlebars: true }, 'html.mirrorCursorOnMatchingTag'); toDispose.push(disposable); disposable = client.onTelemetry(e => { diff --git a/extensions/html-language-features/package.json b/extensions/html-language-features/package.json index 5dedb79987b..696c2dda852 100644 --- a/extensions/html-language-features/package.json +++ b/extensions/html-language-features/package.json @@ -161,11 +161,11 @@ "default": true, "description": "%html.autoClosingTags%" }, - "html.autoSelectingMatchingTags": { + "html.mirrorCursorOnMatchingTag": { "type": "boolean", "scope": "resource", "default": true, - "description": "%html.autoSelectingMatchingTags%" + "description": "%html.mirrorCursorOnMatchingTag%" }, "html.trace.server": { "type": "string", diff --git a/extensions/html-language-features/package.nls.json b/extensions/html-language-features/package.nls.json index 4e4d666b9f7..8fa34d25270 100644 --- a/extensions/html-language-features/package.nls.json +++ b/extensions/html-language-features/package.nls.json @@ -25,5 +25,5 @@ "html.validate.scripts": "Controls whether the built-in HTML language support validates embedded scripts.", "html.validate.styles": "Controls whether the built-in HTML language support validates embedded styles.", "html.autoClosingTags": "Enable/disable autoclosing of HTML tags.", - "html.autoSelectingMatchingTags": "Enable/disable auto selecting matching HTML tags." + "html.mirrorCursorOnMatchingTag": "Enable/disable mirroring cursor on matching HTML tag." } From f966d27d9adddefd86b05e023332af4f1a58793a Mon Sep 17 00:00:00 2001 From: Pine Wu Date: Wed, 4 Dec 2019 16:58:10 -0800 Subject: [PATCH 195/255] Update css service --- extensions/css-language-features/server/package.json | 2 +- extensions/css-language-features/server/yarn.lock | 8 ++++---- extensions/html-language-features/server/package.json | 2 +- extensions/html-language-features/server/yarn.lock | 8 ++++---- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/extensions/css-language-features/server/package.json b/extensions/css-language-features/server/package.json index c5aacd3d41c..5ff5b49c5f3 100644 --- a/extensions/css-language-features/server/package.json +++ b/extensions/css-language-features/server/package.json @@ -9,7 +9,7 @@ }, "main": "./out/cssServerMain", "dependencies": { - "vscode-css-languageservice": "^4.0.3-next.22", + "vscode-css-languageservice": "^4.0.3-next.23", "vscode-languageserver": "^6.0.0-next.3" }, "devDependencies": { diff --git a/extensions/css-language-features/server/yarn.lock b/extensions/css-language-features/server/yarn.lock index 00e2de5a14b..6191a8ffcd5 100644 --- a/extensions/css-language-features/server/yarn.lock +++ b/extensions/css-language-features/server/yarn.lock @@ -781,10 +781,10 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" -vscode-css-languageservice@^4.0.3-next.22: - version "4.0.3-next.22" - resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-4.0.3-next.22.tgz#ddb23a7086807f0e098f069ad91a8e3b7a696cbb" - integrity sha512-j7+S/0LPn/pTe/CQ8WscDttW5J3aY5Ls6bo/Cz8ZdlBjHl7DbrbZ2rEl5Qd6aKbg0ZdgjWVxGqjjgZFzh+O9/w== +vscode-css-languageservice@^4.0.3-next.23: + version "4.0.3-next.23" + resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-4.0.3-next.23.tgz#b7a835c317a6f0b96ec104e3ce168770f5f0d4ff" + integrity sha512-DmMXBzTd3uYnVMffNlcp+Sy4qdzeE+UG5yivo/5Y1f/Qr//4FyssH0eCZ7K9Vf/9DMKYf9J3HeCNZSTq4EzMrg== dependencies: vscode-languageserver-textdocument "^1.0.0-next.4" vscode-languageserver-types "^3.15.0-next.6" diff --git a/extensions/html-language-features/server/package.json b/extensions/html-language-features/server/package.json index 2cc1a5b2323..27661b072bd 100644 --- a/extensions/html-language-features/server/package.json +++ b/extensions/html-language-features/server/package.json @@ -9,7 +9,7 @@ }, "main": "./out/htmlServerMain", "dependencies": { - "vscode-css-languageservice": "^4.0.3-next.22", + "vscode-css-languageservice": "^4.0.3-next.23", "vscode-html-languageservice": "^3.0.4-next.11", "vscode-languageserver": "^6.0.0-next.3", "vscode-nls": "^4.1.1", diff --git a/extensions/html-language-features/server/yarn.lock b/extensions/html-language-features/server/yarn.lock index 3185f2943f6..395953969f4 100644 --- a/extensions/html-language-features/server/yarn.lock +++ b/extensions/html-language-features/server/yarn.lock @@ -611,10 +611,10 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" -vscode-css-languageservice@^4.0.3-next.22: - version "4.0.3-next.22" - resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-4.0.3-next.22.tgz#ddb23a7086807f0e098f069ad91a8e3b7a696cbb" - integrity sha512-j7+S/0LPn/pTe/CQ8WscDttW5J3aY5Ls6bo/Cz8ZdlBjHl7DbrbZ2rEl5Qd6aKbg0ZdgjWVxGqjjgZFzh+O9/w== +vscode-css-languageservice@^4.0.3-next.23: + version "4.0.3-next.23" + resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-4.0.3-next.23.tgz#b7a835c317a6f0b96ec104e3ce168770f5f0d4ff" + integrity sha512-DmMXBzTd3uYnVMffNlcp+Sy4qdzeE+UG5yivo/5Y1f/Qr//4FyssH0eCZ7K9Vf/9DMKYf9J3HeCNZSTq4EzMrg== dependencies: vscode-languageserver-textdocument "^1.0.0-next.4" vscode-languageserver-types "^3.15.0-next.6" From bbf00d8ea6aa7e825ca3393364d746fe401d3299 Mon Sep 17 00:00:00 2001 From: SteVen Batten <6561887+sbatten@users.noreply.github.com> Date: Wed, 4 Dec 2019 18:06:46 -0800 Subject: [PATCH 196/255] attempt #2 (#86354) * Another attempt * tweak titlebar z index --- src/vs/workbench/browser/media/part.css | 5 +++++ .../browser/parts/activitybar/media/activitybarpart.css | 1 + .../workbench/browser/parts/sidebar/media/sidebarpart.css | 6 ++---- .../workbench/browser/parts/titlebar/media/titlebarpart.css | 2 +- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/browser/media/part.css b/src/vs/workbench/browser/media/part.css index 89dd7fe4c9b..d71cf8f50d1 100644 --- a/src/vs/workbench/browser/media/part.css +++ b/src/vs/workbench/browser/media/part.css @@ -8,6 +8,11 @@ overflow: hidden; } +.monaco-workbench.windows.chromium .part:focus-within, +.monaco-workbench.linux.chromium .part:focus-within { + z-index: 500; +} + .monaco-workbench .part > .title { display: none; /* Parts have to opt in to show title area */ } diff --git a/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css b/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css index 09bb59d2980..4dc487ae5d0 100644 --- a/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css +++ b/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css @@ -20,6 +20,7 @@ */ transform: translate3d(0px, 0px, 0px); overflow: visible; /* when a new layer is created, we need to set overflow visible to avoid clipping the menubar */ + position: relative; } .monaco-workbench .activitybar > .content { diff --git a/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css b/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css index 1e2fad096a3..ac2845f9c4b 100644 --- a/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css +++ b/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css @@ -15,10 +15,8 @@ * macOS: does not render LCD-anti-aliased. */ transform: translate3d(0px, 0px, 0px); -} - -.monaco-workbench .part.sidebar > .content { - overflow: hidden; + overflow: visible; + position: relative; } .monaco-workbench.nosidebar > .part.sidebar { diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index 54c67324b61..e71467abfc9 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -32,7 +32,7 @@ */ transform: translate3d(0px, 0px, 0px); position: relative; - z-index: 1000; /* move the entire titlebar above the workbench, except modals/dialogs */ + z-index: 1500 !important; /* move the entire titlebar above the workbench, except modals/dialogs */ } .monaco-workbench .part.titlebar > .titlebar-drag-region { From 52de2192507804ec088387bd996d6e6f171c9ef2 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 5 Dec 2019 07:59:09 +0100 Subject: [PATCH 197/255] Fix #86353 --- src/vs/workbench/browser/parts/editor/editorStatus.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 71ac2878d4e..848ff70c8d7 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -851,6 +851,7 @@ class ShowCurrentMarkerInStatusbarContribution extends Disposable { update(editor: ICodeEditor | undefined): void { this.editor = editor; + this.updateMarkers(); this.updateStatus(); } From d2c5d5e1184ca78d051813b17255568158bf8f3a Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 5 Dec 2019 08:13:14 +0100 Subject: [PATCH 198/255] Fix #86292 --- .../electron-browser/media/runtimeExtensionsEditor.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/workbench/contrib/extensions/electron-browser/media/runtimeExtensionsEditor.css b/src/vs/workbench/contrib/extensions/electron-browser/media/runtimeExtensionsEditor.css index cedd8541829..47a318e4411 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/media/runtimeExtensionsEditor.css +++ b/src/vs/workbench/contrib/extensions/electron-browser/media/runtimeExtensionsEditor.css @@ -22,6 +22,10 @@ font-weight: bold; } +.runtime-extensions-editor .extension .desc .msg .codicon { + vertical-align: middle; +} + .runtime-extensions-editor .extension .time { padding: 4px; text-align: right; From f3f658369f117d12b0ae6960555b4b2845178fd8 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 5 Dec 2019 09:24:39 +0100 Subject: [PATCH 199/255] fix #86067 --- .../userDataSync/common/keybindingsSync.ts | 25 +++---------------- .../userDataSync/common/settingsSync.ts | 24 +++--------------- 2 files changed, 8 insertions(+), 41 deletions(-) diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index f278643a849..269065cd9a0 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -11,10 +11,10 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { parse, ParseError } from 'vs/base/common/json'; import { localize } from 'vs/nls'; import { Emitter, Event } from 'vs/base/common/event'; -import { CancelablePromise, createCancelablePromise, ThrottledDelayer } from 'vs/base/common/async'; +import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { URI } from 'vs/base/common/uri'; -import { joinPath } from 'vs/base/common/resources'; +import { joinPath, dirname } from 'vs/base/common/resources'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { CancellationToken } from 'vs/base/common/cancellation'; import { OS, OperatingSystem } from 'vs/base/common/platform'; @@ -46,7 +46,6 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser private _onDidChangStatus: Emitter = this._register(new Emitter()); readonly onDidChangeStatus: Event = this._onDidChangStatus.event; - private readonly throttledDelayer: ThrottledDelayer; private _onDidChangeLocal: Emitter = this._register(new Emitter()); readonly onDidChangeLocal: Event = this._onDidChangeLocal.event; @@ -62,24 +61,8 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser ) { super(); this.lastSyncKeybindingsResource = joinPath(this.environmentService.userRoamingDataHome, '.lastSyncKeybindings.json'); - this.throttledDelayer = this._register(new ThrottledDelayer(500)); - this._register(this.fileService.watch(this.environmentService.keybindingsResource)); - this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.environmentService.keybindingsResource))(() => this.throttledDelayer.trigger(() => this.onDidChangeKeybindings()))); - } - - private async onDidChangeKeybindings(): Promise { - const localFileContent = await this.getLocalContent(); - const lastSyncData = await this.getLastSyncUserData(); - if (localFileContent && lastSyncData) { - if (localFileContent.value.toString() !== lastSyncData.content) { - this._onDidChangeLocal.fire(); - return; - } - } - if (!localFileContent || !lastSyncData) { - this._onDidChangeLocal.fire(); - return; - } + this._register(this.fileService.watch(dirname(this.environmentService.keybindingsResource))); + this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.environmentService.keybindingsResource))(() => this._onDidChangeLocal.fire())); } private setStatus(status: SyncStatus): void { diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index ecefa974966..4532f40b9dd 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -10,10 +10,10 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { parse, ParseError } from 'vs/base/common/json'; import { localize } from 'vs/nls'; import { Emitter, Event } from 'vs/base/common/event'; -import { CancelablePromise, createCancelablePromise, ThrottledDelayer } from 'vs/base/common/async'; +import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { URI } from 'vs/base/common/uri'; -import { joinPath } from 'vs/base/common/resources'; +import { joinPath, dirname } from 'vs/base/common/resources'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { startsWith } from 'vs/base/common/strings'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -37,7 +37,6 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { private _onDidChangStatus: Emitter = this._register(new Emitter()); readonly onDidChangeStatus: Event = this._onDidChangStatus.event; - private readonly throttledDelayer: ThrottledDelayer; private _onDidChangeLocal: Emitter = this._register(new Emitter()); readonly onDidChangeLocal: Event = this._onDidChangeLocal.event; @@ -53,23 +52,8 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { ) { super(); this.lastSyncSettingsResource = joinPath(this.environmentService.userRoamingDataHome, '.lastSyncSettings.json'); - this.throttledDelayer = this._register(new ThrottledDelayer(500)); - this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.environmentService.settingsResource))(() => this.throttledDelayer.trigger(() => this.onDidChangeSettings()))); - } - - private async onDidChangeSettings(): Promise { - const localFileContent = await this.getLocalFileContent(); - const lastSyncData = await this.getLastSyncUserData(); - if (localFileContent && lastSyncData) { - if (localFileContent.value.toString() !== lastSyncData.content) { - this._onDidChangeLocal.fire(); - return; - } - } - if (!localFileContent || !lastSyncData) { - this._onDidChangeLocal.fire(); - return; - } + this._register(this.fileService.watch(dirname(this.environmentService.settingsResource))); + this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.environmentService.settingsResource))(() => this._onDidChangeLocal.fire())); } private setStatus(status: SyncStatus): void { From 7cbfeb67921a1dd954a4b4f9b3e45479a63cb1cb Mon Sep 17 00:00:00 2001 From: isidor Date: Thu, 5 Dec 2019 09:37:22 +0100 Subject: [PATCH 200/255] fixes #86341 --- src/vs/workbench/contrib/debug/browser/callStackView.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index f62363e4bec..6f0843b73db 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -505,7 +505,7 @@ class StackFramesRenderer implements ITreeRenderer Date: Thu, 5 Dec 2019 09:58:17 +0100 Subject: [PATCH 201/255] Fix #82567 --- src/vs/platform/files/common/files.ts | 15 ++++++++++++++ src/vs/platform/log/common/fileLogService.ts | 7 +++++-- .../contrib/logs/common/logs.contribution.ts | 3 ++- .../configuration/browser/configuration.ts | 20 +++---------------- 4 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index 658998bd7c2..83ed4656a1b 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -819,3 +819,18 @@ export function etag(stat: { mtime: number | undefined, size: number | undefined return stat.mtime.toString(29) + stat.size.toString(31); } + + +export function whenProviderRegistered(file: URI, fileService: IFileService): Promise { + if (fileService.canHandleResource(URI.from({ scheme: file.scheme }))) { + return Promise.resolve(); + } + return new Promise((c, e) => { + const disposable = fileService.onDidChangeFileSystemProviderRegistrations(e => { + if (e.scheme === file.scheme && e.added) { + disposable.dispose(); + c(); + } + }); + }); +} diff --git a/src/vs/platform/log/common/fileLogService.ts b/src/vs/platform/log/common/fileLogService.ts index e8bc1ae9005..32493332bc1 100644 --- a/src/vs/platform/log/common/fileLogService.ts +++ b/src/vs/platform/log/common/fileLogService.ts @@ -5,12 +5,13 @@ import { ILogService, LogLevel, AbstractLogService, ILoggerService, ILogger } from 'vs/platform/log/common/log'; import { URI } from 'vs/base/common/uri'; -import { IFileService } from 'vs/platform/files/common/files'; +import { IFileService, whenProviderRegistered } from 'vs/platform/files/common/files'; import { Queue } from 'vs/base/common/async'; import { VSBuffer } from 'vs/base/common/buffer'; import { dirname, joinPath, basename } from 'vs/base/common/resources'; import { Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { BufferLogService } from 'vs/platform/log/common/bufferLog'; const MAX_FILE_SIZE = 1024 * 1024 * 5; @@ -163,6 +164,7 @@ export class FileLoggerService extends Disposable implements ILoggerService { constructor( @ILogService private logService: ILogService, @IInstantiationService private instantiationService: IInstantiationService, + @IFileService private fileService: IFileService, ) { super(); this._register(logService.onDidChangeLogLevel(level => this.loggers.forEach(logger => logger.setLevel(level)))); @@ -171,8 +173,9 @@ export class FileLoggerService extends Disposable implements ILoggerService { getLogger(resource: URI): ILogger { let logger = this.loggers.get(resource.toString()); if (!logger) { - logger = this.instantiationService.createInstance(FileLogService, basename(resource), resource, this.logService.getLevel()); + logger = new BufferLogService, this.logService.getLevel(); this.loggers.set(resource.toString(), logger); + whenProviderRegistered(resource, this.fileService).then(() => (logger).logger = this.instantiationService.createInstance(FileLogService, basename(resource), resource, this.logService.getLevel())); } return logger; } diff --git a/src/vs/workbench/contrib/logs/common/logs.contribution.ts b/src/vs/workbench/contrib/logs/common/logs.contribution.ts index 70a088ed9f8..09b25d44c91 100644 --- a/src/vs/workbench/contrib/logs/common/logs.contribution.ts +++ b/src/vs/workbench/contrib/logs/common/logs.contribution.ts @@ -12,7 +12,7 @@ import { SetLogLevelAction, OpenWindowSessionLogFileAction } from 'vs/workbench/ import * as Constants from 'vs/workbench/contrib/logs/common/logConstants'; import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IFileService, FileChangeType } from 'vs/platform/files/common/files'; +import { IFileService, FileChangeType, whenProviderRegistered } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import { IOutputChannelRegistry, Extensions as OutputExt } from 'vs/workbench/contrib/output/common/output'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -71,6 +71,7 @@ class LogOutputChannels extends Disposable implements IWorkbenchContribution { } private async registerLogChannel(id: string, label: string, file: URI): Promise { + await whenProviderRegistered(file, this.fileService); const outputChannelRegistry = Registry.as(OutputExt.OutputChannels); const exists = await this.fileService.exists(file); if (exists) { diff --git a/src/vs/workbench/services/configuration/browser/configuration.ts b/src/vs/workbench/services/configuration/browser/configuration.ts index d3af9e42aee..d2450a267f7 100644 --- a/src/vs/workbench/services/configuration/browser/configuration.ts +++ b/src/vs/workbench/services/configuration/browser/configuration.ts @@ -9,7 +9,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import * as errors from 'vs/base/common/errors'; import { Disposable, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { RunOnceScheduler } from 'vs/base/common/async'; -import { FileChangeType, FileChangesEvent, IFileService } from 'vs/platform/files/common/files'; +import { FileChangeType, FileChangesEvent, IFileService, whenProviderRegistered } from 'vs/platform/files/common/files'; import { ConfigurationModel, ConfigurationModelParser } from 'vs/platform/configuration/common/configurationModels'; import { WorkspaceConfigurationModelParser, StandaloneConfigurationModelParser } from 'vs/workbench/services/configuration/common/configurationModels'; import { FOLDER_SETTINGS_PATH, TASKS_CONFIGURATION_KEY, FOLDER_SETTINGS_NAME, LAUNCH_CONFIGURATION_KEY, IConfigurationCache, ConfigurationKey, REMOTE_MACHINE_SCOPES, FOLDER_SCOPES, WORKSPACE_SCOPES } from 'vs/workbench/services/configuration/common/configuration'; @@ -24,20 +24,6 @@ import { IConfigurationModel } from 'vs/platform/configuration/common/configurat import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { hash } from 'vs/base/common/hash'; -function whenProviderRegistered(scheme: string, fileService: IFileService): Promise { - if (fileService.canHandleResource(URI.from({ scheme }))) { - return Promise.resolve(); - } - return new Promise((c, e) => { - const disposable = fileService.onDidChangeFileSystemProviderRegistrations(e => { - if (e.scheme === scheme && e.added) { - disposable.dispose(); - c(); - } - }); - }); -} - export class UserConfiguration extends Disposable { private readonly parser: ConfigurationModelParser; @@ -353,7 +339,7 @@ export class WorkspaceConfiguration extends Disposable { } private async waitAndSwitch(workspaceIdentifier: IWorkspaceIdentifier): Promise { - await whenProviderRegistered(workspaceIdentifier.configPath.scheme, this._fileService); + await whenProviderRegistered(workspaceIdentifier.configPath, this._fileService); if (!(this._workspaceConfiguration instanceof FileServiceBasedWorkspaceConfiguration)) { const fileServiceBasedWorkspaceConfiguration = this._register(new FileServiceBasedWorkspaceConfiguration(this._fileService)); await fileServiceBasedWorkspaceConfiguration.load(workspaceIdentifier); @@ -750,7 +736,7 @@ export class FolderConfiguration extends Disposable implements IFolderConfigurat if (workspaceFolder.uri.scheme === Schemas.file) { this.folderConfiguration = new FileServiceBasedFolderConfiguration(this.configurationFolder, this.workbenchState, fileService); } else { - whenProviderRegistered(workspaceFolder.uri.scheme, fileService) + whenProviderRegistered(workspaceFolder.uri, fileService) .then(() => { this.folderConfiguration.dispose(); this.folderConfigurationDisposable.dispose(); From 8878e56034113032198fac1ba008808f5af2a897 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 5 Dec 2019 10:22:27 +0100 Subject: [PATCH 202/255] Garbled characters when importing multiple folders into the workspace (fix #85870) --- .../textfile/electron-browser/nativeTextFileService.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts index 9b748aef08b..7cd4ad05439 100644 --- a/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts +++ b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts @@ -350,8 +350,9 @@ export class EncodingOracle extends Disposable implements IResourceEncodings { // Global settings defaultEncodingOverrides.push({ parent: this.environmentService.userRoamingDataHome, encoding: UTF8 }); - // Workspace files + // Workspace files (via extension and via untitled workspaces location) defaultEncodingOverrides.push({ extension: WORKSPACE_EXTENSION, encoding: UTF8 }); + defaultEncodingOverrides.push({ parent: this.environmentService.untitledWorkspacesHome, encoding: UTF8 }); // Folder Settings this.contextService.getWorkspace().folders.forEach(folder => { From 98ccdc7ba4affee8d6382ab3442bc39f9dd0a27b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 5 Dec 2019 10:35:27 +0100 Subject: [PATCH 203/255] encoding - only allow to detect UTF8 automatically --- src/vs/base/node/encoding.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/vs/base/node/encoding.ts b/src/vs/base/node/encoding.ts index 3fbae109f00..8da85d8ce39 100644 --- a/src/vs/base/node/encoding.ts +++ b/src/vs/base/node/encoding.ts @@ -199,6 +199,13 @@ export function detectEncodingByBOMFromBuffer(buffer: Buffer | VSBuffer | null, return null; } +// we explicitly ignore a specific set of encodings from auto guessing +// - ASCII: we never want this encoding (most UTF-8 files would happily detect as +// ASCII files and then you could not type non-ASCII characters anymore) +// - UTF-16: we have our own detection logic for UTF-16 +// - UTF-32: we do not support this encoding in VSCode +const IGNORE_ENCODINGS = ['ascii', 'utf-16', 'utf-32']; + /** * Guesses the encoding from buffer. */ @@ -210,15 +217,9 @@ async function guessEncodingByBuffer(buffer: Buffer): Promise { return null; } - // Ignore 'ascii' as guessed encoding because that - // is almost never what we want, rather fallback - // to the configured encoding then. Otherwise, - // opening a ascii-only file with auto guessing - // enabled will put the file into 'ascii' mode - // and thus typing any special characters is - // not possible anymore. - if (guessed.encoding.toLowerCase() === 'ascii') { - return null; + const enc = guessed.encoding.toLowerCase(); + if (0 <= IGNORE_ENCODINGS.indexOf(enc)) { + return null; // see comment above why we ignore some encodings } return toIconvLiteEncoding(guessed.encoding); From 7f257cd4fe9ec8e9757491ac0b051912c141bbe9 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 5 Dec 2019 10:12:27 +0100 Subject: [PATCH 204/255] update references view extension, #86305 --- build/builtInExtensions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/builtInExtensions.json b/build/builtInExtensions.json index e2f11ebf0b3..630aa51788e 100644 --- a/build/builtInExtensions.json +++ b/build/builtInExtensions.json @@ -31,7 +31,7 @@ }, { "name": "ms-vscode.references-view", - "version": "0.0.40", + "version": "0.0.41", "repo": "https://github.com/Microsoft/vscode-reference-view", "metadata": { "id": "dc489f46-520d-4556-ae85-1f9eab3c412d", From 6b2b68207e264a724a9d5db472d8520da0db33b4 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 5 Dec 2019 10:43:33 +0100 Subject: [PATCH 205/255] also update peek icons, #86305 --- .../callHierarchy/browser/callHierarchyPeek.ts | 4 ++-- .../browser/media/action-call-from-dark.svg | 3 +++ .../callHierarchy/browser/media/action-call-from.svg | 3 +++ .../browser/media/action-call-to-dark.svg | 3 +++ .../callHierarchy/browser/media/action-call-to.svg | 3 +++ .../callHierarchy/browser/media/callHierarchy.css | 12 ++++++++++-- .../browser/media/files_CallFrom_CallFrom_16x.svg | 1 - .../browser/media/files_CallTo_CallTo_16x.svg | 1 - 8 files changed, 24 insertions(+), 6 deletions(-) create mode 100644 src/vs/workbench/contrib/callHierarchy/browser/media/action-call-from-dark.svg create mode 100644 src/vs/workbench/contrib/callHierarchy/browser/media/action-call-from.svg create mode 100644 src/vs/workbench/contrib/callHierarchy/browser/media/action-call-to-dark.svg create mode 100644 src/vs/workbench/contrib/callHierarchy/browser/media/action-call-to.svg delete mode 100644 src/vs/workbench/contrib/callHierarchy/browser/media/files_CallFrom_CallFrom_16x.svg delete mode 100644 src/vs/workbench/contrib/callHierarchy/browser/media/files_CallTo_CallTo_16x.svg diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts index 25fe8bade28..bce21ec2671 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts @@ -49,10 +49,10 @@ class ChangeHierarchyDirectionAction extends Action { }); const update = () => { if (getDirection() === CallHierarchyDirection.CallsFrom) { - this.label = localize('toggle.from', "Showing Calls"); + this.label = localize('toggle.from', "Show Incoming Calls"); this.class = 'calls-from'; } else { - this.label = localize('toggle.to', "Showing Callers"); + this.label = localize('toggle.to', "Showing Outgoing Calls"); this.class = 'calls-to'; } }; diff --git a/src/vs/workbench/contrib/callHierarchy/browser/media/action-call-from-dark.svg b/src/vs/workbench/contrib/callHierarchy/browser/media/action-call-from-dark.svg new file mode 100644 index 00000000000..66406bfc5dd --- /dev/null +++ b/src/vs/workbench/contrib/callHierarchy/browser/media/action-call-from-dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/contrib/callHierarchy/browser/media/action-call-from.svg b/src/vs/workbench/contrib/callHierarchy/browser/media/action-call-from.svg new file mode 100644 index 00000000000..b65e2d14a4d --- /dev/null +++ b/src/vs/workbench/contrib/callHierarchy/browser/media/action-call-from.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/contrib/callHierarchy/browser/media/action-call-to-dark.svg b/src/vs/workbench/contrib/callHierarchy/browser/media/action-call-to-dark.svg new file mode 100644 index 00000000000..ff488f1ed4c --- /dev/null +++ b/src/vs/workbench/contrib/callHierarchy/browser/media/action-call-to-dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/contrib/callHierarchy/browser/media/action-call-to.svg b/src/vs/workbench/contrib/callHierarchy/browser/media/action-call-to.svg new file mode 100644 index 00000000000..159e5b92eaa --- /dev/null +++ b/src/vs/workbench/contrib/callHierarchy/browser/media/action-call-to.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/contrib/callHierarchy/browser/media/callHierarchy.css b/src/vs/workbench/contrib/callHierarchy/browser/media/callHierarchy.css index 19c2e871295..c4e553dbd9f 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/media/callHierarchy.css +++ b/src/vs/workbench/contrib/callHierarchy/browser/media/callHierarchy.css @@ -21,19 +21,27 @@ } .monaco-workbench .action-label.calls-to { - background-image: url(files_CallTo_CallTo_16x.svg); + background-image: url(action-call-to.svg); background-size: 14px 14px; background-repeat: no-repeat; background-position: left center; } +.vs-dark .monaco-workbench .action-label.calls-to { + background-image: url(action-call-to-dark.svg); +} + .monaco-workbench .action-label.calls-from { - background-image: url(files_CallFrom_CallFrom_16x.svg); + background-image: url(action-call-from.svg); background-size: 14px 14px; background-repeat: no-repeat; background-position: left center; } +.vs-dark .monaco-workbench .action-label.calls-from{ + background-image: url(action-call-from-dark.svg); +} + .monaco-workbench .call-hierarchy .editor, .monaco-workbench .call-hierarchy .tree { height: 100%; diff --git a/src/vs/workbench/contrib/callHierarchy/browser/media/files_CallFrom_CallFrom_16x.svg b/src/vs/workbench/contrib/callHierarchy/browser/media/files_CallFrom_CallFrom_16x.svg deleted file mode 100644 index 667f3e7655b..00000000000 --- a/src/vs/workbench/contrib/callHierarchy/browser/media/files_CallFrom_CallFrom_16x.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/workbench/contrib/callHierarchy/browser/media/files_CallTo_CallTo_16x.svg b/src/vs/workbench/contrib/callHierarchy/browser/media/files_CallTo_CallTo_16x.svg deleted file mode 100644 index 26c436e77c9..00000000000 --- a/src/vs/workbench/contrib/callHierarchy/browser/media/files_CallTo_CallTo_16x.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file From ebd9a08e240b2147acf6c6b312fe21a25a0d957b Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 5 Dec 2019 10:47:26 +0100 Subject: [PATCH 206/255] update ref view extension, https://github.com/microsoft/vscode/issues/86125 --- build/builtInExtensions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/builtInExtensions.json b/build/builtInExtensions.json index 630aa51788e..3303d285507 100644 --- a/build/builtInExtensions.json +++ b/build/builtInExtensions.json @@ -31,7 +31,7 @@ }, { "name": "ms-vscode.references-view", - "version": "0.0.41", + "version": "0.0.42", "repo": "https://github.com/Microsoft/vscode-reference-view", "metadata": { "id": "dc489f46-520d-4556-ae85-1f9eab3c412d", From b1ed55a74acc5296e3f1efd6068a400173ec0c73 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 5 Dec 2019 10:56:32 +0100 Subject: [PATCH 207/255] fix #86319 --- .../contrib/callHierarchy/browser/callHierarchyPeek.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts index bce21ec2671..c85cd79025d 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts @@ -428,7 +428,7 @@ export class CallHierarchyTreePeekWidget extends peekView.PeekViewWidget { } protected _doLayoutBody(height: number, width: number): void { - if (this._dim.height !== height || this._dim.width === width) { + if (this._dim.height !== height || this._dim.width !== width) { super._doLayoutBody(height, width); this._dim = { height, width }; this._layoutInfo.height = this._viewZone ? this._viewZone.heightInLines : this._layoutInfo.height; From d7953d0324acb2a2cfdeaead2494658fa218de0a Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 5 Dec 2019 11:04:46 +0100 Subject: [PATCH 208/255] fix #86321 --- .../callHierarchy/browser/callHierarchy.contribution.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts index 4f5199b62ed..55b2be2978c 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts @@ -24,7 +24,7 @@ import { Range } from 'vs/editor/common/core/range'; import { IPosition } from 'vs/editor/common/core/position'; import { MenuId } from 'vs/platform/actions/common/actions'; -const _ctxHasCompletionItemProvider = new RawContextKey('editorHasCallHierarchyProvider', false); +const _ctxHasCallHierarchyProvider = new RawContextKey('editorHasCallHierarchyProvider', false); const _ctxCallHierarchyVisible = new RawContextKey('callHierarchyVisible', false); class CallHierarchyController implements IEditorContribution { @@ -52,7 +52,7 @@ class CallHierarchyController implements IEditorContribution { @IInstantiationService private readonly _instantiationService: IInstantiationService, ) { this._ctxIsVisible = _ctxCallHierarchyVisible.bindTo(this._contextKeyService); - this._ctxHasProvider = _ctxHasCompletionItemProvider.bindTo(this._contextKeyService); + this._ctxHasProvider = _ctxHasCallHierarchyProvider.bindTo(this._contextKeyService); this._dispoables.add(Event.any(_editor.onDidChangeModel, _editor.onDidChangeModelLanguage, CallHierarchyProviderRegistry.onDidChange)(() => { this._ctxHasProvider.set(_editor.hasModel() && CallHierarchyProviderRegistry.has(_editor.getModel())); })); @@ -172,8 +172,7 @@ registerEditorAction(class extends EditorAction { primary: KeyMod.Shift + KeyMod.Alt + KeyCode.KEY_H }, precondition: ContextKeyExpr.and( - _ctxCallHierarchyVisible.negate(), - _ctxHasCompletionItemProvider, + _ctxHasCallHierarchyProvider, PeekContext.notInPeekEditor ) }); From eeee6244d9b2277126b8c758df9f9fe4eb30e1bc Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 5 Dec 2019 11:15:33 +0100 Subject: [PATCH 209/255] [josn] fix wrong schema-schema reference, use draft-07 everywhere --- .../configuration-editing/schemas/attachContainer.schema.json | 2 +- .../configuration-editing/schemas/devContainer.schema.json | 2 +- extensions/css-language-features/schemas/package.schema.json | 2 +- extensions/html-language-features/schemas/package.schema.json | 2 +- .../markdown-language-features/schemas/package.schema.json | 4 ++-- .../typescript-language-features/schemas/package.schema.json | 4 ++-- .../workbench/contrib/tasks/common/taskDefinitionRegistry.ts | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/extensions/configuration-editing/schemas/attachContainer.schema.json b/extensions/configuration-editing/schemas/attachContainer.schema.json index 65f79624518..cebd0f2bc3e 100644 --- a/extensions/configuration-editing/schemas/attachContainer.schema.json +++ b/extensions/configuration-editing/schemas/attachContainer.schema.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/schema#", + "$schema": "http://json-schema.org/draft-07/schema#", "description": "Configures an attached to container", "allowComments": true, "type": "object", diff --git a/extensions/configuration-editing/schemas/devContainer.schema.json b/extensions/configuration-editing/schemas/devContainer.schema.json index 710c2a26c60..aed58455d01 100644 --- a/extensions/configuration-editing/schemas/devContainer.schema.json +++ b/extensions/configuration-editing/schemas/devContainer.schema.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/schema#", + "$schema": "http://json-schema.org/draft-07/schema#", "description": "Defines a dev container", "allowComments": true, "type": "object", diff --git a/extensions/css-language-features/schemas/package.schema.json b/extensions/css-language-features/schemas/package.schema.json index 3aeca1e040a..cf4193008ec 100644 --- a/extensions/css-language-features/schemas/package.schema.json +++ b/extensions/css-language-features/schemas/package.schema.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-04/schema#", + "$schema": "http://json-schema.org/draft-07/schema#", "title": "CSS contributions to package.json", "type": "object", "properties": { diff --git a/extensions/html-language-features/schemas/package.schema.json b/extensions/html-language-features/schemas/package.schema.json index aaf9588221e..a11810ef090 100644 --- a/extensions/html-language-features/schemas/package.schema.json +++ b/extensions/html-language-features/schemas/package.schema.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-04/schema#", + "$schema": "http://json-schema.org/draft-07/schema#", "title": "HTML contributions to package.json", "type": "object", "properties": { diff --git a/extensions/markdown-language-features/schemas/package.schema.json b/extensions/markdown-language-features/schemas/package.schema.json index e76ce046c27..5591d0b0032 100644 --- a/extensions/markdown-language-features/schemas/package.schema.json +++ b/extensions/markdown-language-features/schemas/package.schema.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-04/schema#", + "$schema": "http://json-schema.org/draft-07/schema#", "title": "Markdown contributions to package.json", "type": "object", "properties": { @@ -29,4 +29,4 @@ } } } -} \ No newline at end of file +} diff --git a/extensions/typescript-language-features/schemas/package.schema.json b/extensions/typescript-language-features/schemas/package.schema.json index c26af63c93b..c135ea39ec1 100644 --- a/extensions/typescript-language-features/schemas/package.schema.json +++ b/extensions/typescript-language-features/schemas/package.schema.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-04/schema#", + "$schema": "http://json-schema.org/draft-07/schema#", "title": "TypeScript contributions to package.json", "type": "object", "properties": { @@ -28,4 +28,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts b/src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts index 6a696c1666a..1526a62d12b 100644 --- a/src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts +++ b/src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts @@ -33,7 +33,7 @@ const taskDefinitionSchema: IJSONSchema = { type: 'object', description: nls.localize('TaskDefinition.properties', 'Additional properties of the task type'), additionalProperties: { - $ref: 'http://json-schema.org/draft-04/schema#' + $ref: 'http://json-schema.org/draft-07/schema#' } } } From 64b177b2e0bc2360db1aa2c1eb8b87a511c844f1 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 5 Dec 2019 11:22:40 +0100 Subject: [PATCH 210/255] fix #86300 --- src/vs/workbench/api/common/extHostLanguageFeatures.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 3aa55dc26da..76a9b14643b 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -1209,11 +1209,10 @@ class CallHierarchyAdapter { } releaseSession(sessionId: string): void { - this._cache.delete(sessionId.charAt(0)); + this._cache.delete(sessionId); } - private _cacheAndConvertItem(itemOrSessionId: string, item: vscode.CallHierarchyItem): extHostProtocol.ICallHierarchyItemDto { - const sessionId = itemOrSessionId.charAt(0); + private _cacheAndConvertItem(sessionId: string, item: vscode.CallHierarchyItem): extHostProtocol.ICallHierarchyItemDto { const map = this._cache.get(sessionId)!; const dto: extHostProtocol.ICallHierarchyItemDto = { _sessionId: sessionId, @@ -1231,7 +1230,7 @@ class CallHierarchyAdapter { private _itemFromCache(sessionId: string, itemId: string): vscode.CallHierarchyItem | undefined { const map = this._cache.get(sessionId); - return map && map.get(itemId); + return map?.get(itemId); } } From 29f4c535cbbc952ba69e954b4ccc4dbfb21bbc06 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Thu, 5 Dec 2019 11:49:07 +0100 Subject: [PATCH 211/255] Fixes #86303: Fix adjusting of tokens after the deleted range --- src/vs/editor/common/model/tokensStore.ts | 9 +- .../test/common/model/tokensStore.test.ts | 119 ++++++++++++++++++ 2 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 src/vs/editor/test/common/model/tokensStore.test.ts diff --git a/src/vs/editor/common/model/tokensStore.ts b/src/vs/editor/common/model/tokensStore.ts index aa6953853ca..1df7dcdfb06 100644 --- a/src/vs/editor/common/model/tokensStore.ts +++ b/src/vs/editor/common/model/tokensStore.ts @@ -293,8 +293,13 @@ export class SparseEncodedTokens implements IEncodedTokens { } else if (tokenDeltaLine === endDeltaLine && tokenStartCharacter >= endCharacter) { // 4. (continued) The token starts after the deletion range, on the last line where a deletion occurs tokenDeltaLine -= deletedLineCount; - tokenStartCharacter -= endCharacter; - tokenEndCharacter -= endCharacter; + if (deletedLineCount === 0) { + tokenStartCharacter -= (endCharacter - startCharacter); + tokenEndCharacter -= (endCharacter - startCharacter); + } else { + tokenStartCharacter -= endCharacter; + tokenEndCharacter -= endCharacter; + } } else { throw new Error(`Not possible!`); } diff --git a/src/vs/editor/test/common/model/tokensStore.test.ts b/src/vs/editor/test/common/model/tokensStore.test.ts new file mode 100644 index 00000000000..a7405ea9ab6 --- /dev/null +++ b/src/vs/editor/test/common/model/tokensStore.test.ts @@ -0,0 +1,119 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { MultilineTokens2, SparseEncodedTokens } from 'vs/editor/common/model/tokensStore'; +import { Range } from 'vs/editor/common/core/range'; +import { TextModel } from 'vs/editor/common/model/textModel'; +import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; +import { MetadataConsts, TokenMetadata } from 'vs/editor/common/modes'; + +suite('TokensStore', () => { + + const SEMANTIC_COLOR = 5; + + function parseTokensState(state: string[]): { text: string; tokens: number[]; } { + let text: string[] = []; + let tokens: number[] = []; + for (let i = 0; i < state.length; i++) { + const line = state[i]; + + let startOffset = 0; + let lineText = ''; + while (true) { + const firstPipeOffset = line.indexOf('|', startOffset); + if (firstPipeOffset === -1) { + break; + } + const secondPipeOffset = line.indexOf('|', firstPipeOffset + 1); + if (secondPipeOffset === -1) { + break; + } + if (firstPipeOffset + 1 === secondPipeOffset) { + // skip || + lineText += line.substring(startOffset, secondPipeOffset + 1); + startOffset = secondPipeOffset + 1; + continue; + } + + lineText += line.substring(startOffset, firstPipeOffset); + const tokenStartCharacter = lineText.length; + const tokenLength = secondPipeOffset - firstPipeOffset - 1; + const metadata = (SEMANTIC_COLOR << MetadataConsts.FOREGROUND_OFFSET); + + tokens.push(i, tokenStartCharacter, tokenStartCharacter + tokenLength, metadata); + + lineText += line.substr(firstPipeOffset + 1, tokenLength); + startOffset = secondPipeOffset + 1; + } + + lineText += line.substring(startOffset); + + text.push(lineText); + } + + return { + text: text.join('\n'), + tokens: tokens + }; + } + + function extractState(model: TextModel): string[] { + let result: string[] = []; + for (let lineNumber = 1; lineNumber <= model.getLineCount(); lineNumber++) { + const lineTokens = model.getLineTokens(lineNumber); + const lineContent = model.getLineContent(lineNumber); + + let lineText = ''; + for (let i = 0; i < lineTokens.getCount(); i++) { + const tokenStartCharacter = lineTokens.getStartOffset(i); + const tokenEndCharacter = lineTokens.getEndOffset(i); + const metadata = lineTokens.getMetadata(i); + const color = TokenMetadata.getForeground(metadata); + const tokenText = lineContent.substring(tokenStartCharacter, tokenEndCharacter); + if (color === SEMANTIC_COLOR) { + lineText += `|${tokenText}|`; + } else { + lineText += tokenText; + } + } + + result.push(lineText); + } + return result; + } + + // function extractState + + function testTokensAdjustment(rawInitialState: string[], edits: IIdentifiedSingleEditOperation[], rawFinalState: string[]) { + const initialState = parseTokensState(rawInitialState); + const model = TextModel.createFromString(initialState.text); + model.setSemanticTokens([new MultilineTokens2(1, new SparseEncodedTokens(new Uint32Array(initialState.tokens)))]); + + model.applyEdits(edits); + + const actualState = extractState(model); + assert.deepEqual(actualState, rawFinalState); + + model.dispose(); + } + + test('issue #86303 - color shifting between different tokens', () => { + testTokensAdjustment( + [ + `import { |URI| } from 'vs/base/common/uri';`, + `const foo = |URI|.parse('hey');` + ], + [ + { range: new Range(2, 9, 2, 10), text: '' } + ], + [ + `import { |URI| } from 'vs/base/common/uri';`, + `const fo = |URI|.parse('hey');` + ] + ); + }); + +}); From 02d075ece2a5cbf2c0e3c2bfd6483985c19c23f1 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 5 Dec 2019 12:29:29 +0100 Subject: [PATCH 212/255] Fix #85054 --- .../platform/userDataSync/common/extensionsSync.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index 502384a6731..2cbf0acd173 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -18,6 +18,7 @@ import { startsWith } from 'vs/base/common/strings'; import { IFileService } from 'vs/platform/files/common/files'; import { Queue } from 'vs/base/common/async'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { localize } from 'vs/nls'; export interface ISyncPreviewResult { readonly added: ISyncExtension[]; @@ -323,7 +324,12 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser const extension = await this.extensionGalleryService.getCompatibleExtension(e.identifier, e.version); if (extension) { this.logService.info('Extensions: Installing local extension.', e.identifier.id, extension.version); - await this.extensionManagementService.installFromGallery(extension); + try { + await this.extensionManagementService.installFromGallery(extension); + } catch (e) { + this.logService.error(e); + this.logService.info(localize('skip extension', "Skipping synchronising extension {0}", extension.displayName || extension.identifier.id)); + } } })); } @@ -331,7 +337,9 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser private async getLocalExtensions(): Promise { const installedExtensions = await this.extensionManagementService.getInstalled(ExtensionType.User); - return installedExtensions.map(({ identifier }) => ({ identifier, enabled: true })); + return installedExtensions + .filter(({ identifier }) => !!identifier.uuid) + .map(({ identifier }) => ({ identifier, enabled: true })); } private async getLastSyncUserData(): Promise { From e39717bd97d79289e4960d6df35a26111b4defac Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 5 Dec 2019 12:33:50 +0100 Subject: [PATCH 213/255] Target type in remote explorer help when dropdown doesn't match connection Fixes https://github.com/microsoft/vscode/issues/86328 --- .../contrib/remote/browser/remote.ts | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index 70ab4f4fc52..f8995407609 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -72,7 +72,8 @@ class HelpModel { })), quickInputService, environmentService, - openerService + openerService, + remoteExplorerService )); } @@ -89,7 +90,8 @@ class HelpModel { })), quickInputService, environmentService, - openerService + openerService, + remoteExplorerService )); } @@ -106,7 +108,8 @@ class HelpModel { })), quickInputService, environmentService, - openerService + openerService, + remoteExplorerService )); } @@ -123,7 +126,8 @@ class HelpModel { })), quickInputService, environmentService, - openerService + openerService, + remoteExplorerService )); } @@ -137,7 +141,8 @@ class HelpModel { })), quickInputService, environmentService, - commandService + commandService, + remoteExplorerService )); } @@ -158,14 +163,15 @@ abstract class HelpItemBase implements IHelpItem { public label: string, public values: { extensionDescription: IExtensionDescription, url?: string, remoteAuthority: string[] | undefined }[], private quickInputService: IQuickInputService, - private environmentService: IWorkbenchEnvironmentService + private environmentService: IWorkbenchEnvironmentService, + private remoteExplorerService: IRemoteExplorerService ) { iconClasses.push('remote-help-tree-node-item-icon'); } async handleClick() { const remoteAuthority = this.environmentService.configuration.remoteAuthority; - if (remoteAuthority) { + if (remoteAuthority && startsWith(remoteAuthority, this.remoteExplorerService.targetType)) { for (let value of this.values) { if (value.remoteAuthority) { for (let authority of value.remoteAuthority) { @@ -207,9 +213,10 @@ class HelpItem extends HelpItemBase { values: { extensionDescription: IExtensionDescription; url: string, remoteAuthority: string[] | undefined }[], quickInputService: IQuickInputService, environmentService: IWorkbenchEnvironmentService, - private openerService: IOpenerService + private openerService: IOpenerService, + remoteExplorerService: IRemoteExplorerService ) { - super(iconClasses, label, values, quickInputService, environmentService); + super(iconClasses, label, values, quickInputService, environmentService, remoteExplorerService); } protected async takeAction(extensionDescription: IExtensionDescription, url: string): Promise { @@ -225,8 +232,9 @@ class IssueReporterItem extends HelpItemBase { quickInputService: IQuickInputService, environmentService: IWorkbenchEnvironmentService, private commandService: ICommandService, + remoteExplorerService: IRemoteExplorerService ) { - super(iconClasses, label, values, quickInputService, environmentService); + super(iconClasses, label, values, quickInputService, environmentService, remoteExplorerService); } protected async takeAction(extensionDescription: IExtensionDescription): Promise { From 2936133e67aded3c483167cfddfb07715fef97cf Mon Sep 17 00:00:00 2001 From: isidor Date: Thu, 5 Dec 2019 13:34:58 +0100 Subject: [PATCH 214/255] Ignore stats which are selected but are part of the same compact node as the focused stat fixes #86388 --- src/vs/workbench/contrib/files/browser/views/explorerView.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index f09b093513a..956a7d9fd52 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -297,6 +297,11 @@ export class ExplorerView extends ViewletPane { for (const stat of this.tree.getSelection()) { const controller = this.renderer.getCompressedNavigationController(stat); + if (controller && focusedStat && controller === this.compressedNavigationController && stat !== focusedStat) { + // Ignore stats which are selected but are part of the same compact node as the focused stat + continue; + } + if (controller) { selectedStats.push(...controller.items); } else { From 937c637e04959c80f6f95bef5fb812c6e337041c Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 5 Dec 2019 13:41:28 +0100 Subject: [PATCH 215/255] Proper fix for #85054 --- .../userDataSync/common/extensionsSync.ts | 67 +++++++++++++------ 1 file changed, 47 insertions(+), 20 deletions(-) diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index 2cbf0acd173..2a0a9d7d603 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -27,6 +27,10 @@ export interface ISyncPreviewResult { readonly remote: ISyncExtension[] | null; } +interface ILastSyncUserData extends IUserData { + skippedExtensions: ISyncExtension[] | undefined; +} + export class ExtensionsSynchroniser extends Disposable implements ISynchroniser { private static EXTERNAL_USER_DATA_EXTENSIONS_KEY: string = 'extensions'; @@ -123,14 +127,16 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser private async doSync(): Promise { const lastSyncData = await this.getLastSyncUserData(); - let remoteData = await this.userDataSyncStoreService.read(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, lastSyncData); + const lastSyncExtensions: ISyncExtension[] | null = lastSyncData ? JSON.parse(lastSyncData.content!) : null; + let skippedExtensions: ISyncExtension[] = lastSyncData ? lastSyncData.skippedExtensions || [] : []; - const lastSyncExtensions: ISyncExtension[] = lastSyncData ? JSON.parse(lastSyncData.content!) : null; + let remoteData = await this.userDataSyncStoreService.read(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, lastSyncData); const remoteExtensions: ISyncExtension[] = remoteData.content ? JSON.parse(remoteData.content) : null; + const localExtensions = await this.getLocalExtensions(); this.logService.trace('Extensions: Merging remote extensions with local extensions...'); - const { added, removed, updated, remote } = this.merge(localExtensions, remoteExtensions, lastSyncExtensions); + const { added, removed, updated, remote } = this.merge(localExtensions, remoteExtensions, lastSyncExtensions, skippedExtensions); if (!added.length && !removed.length && !updated.length && !remote) { this.logService.trace('Extensions: No changes found during synchronizing extensions.'); @@ -138,7 +144,7 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser if (added.length || removed.length || updated.length) { this.logService.info('Extensions: Updating local extensions...'); - await this.updateLocalExtensions(added, removed, updated); + skippedExtensions = await this.updateLocalExtensions(added, removed, updated, skippedExtensions); } if (remote) { @@ -152,7 +158,7 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser ) { // update last sync this.logService.info('Extensions: Updating last synchronised extensions...'); - await this.updateLastSyncValue(remoteData); + await this.updateLastSyncValue({ ...remoteData, skippedExtensions }); } } @@ -162,7 +168,7 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser * - Overwrite local with remote changes. Removed, Added, Updated. * - Update remote with those local extension which are newly added or updated or removed and untouched in remote. */ - private merge(localExtensions: ISyncExtension[], remoteExtensions: ISyncExtension[] | null, lastSyncExtensions: ISyncExtension[] | null): { added: ISyncExtension[], removed: IExtensionIdentifier[], updated: ISyncExtension[], remote: ISyncExtension[] | null } { + private merge(localExtensions: ISyncExtension[], remoteExtensions: ISyncExtension[] | null, lastSyncExtensions: ISyncExtension[] | null, skippedExtensions: ISyncExtension[]): { added: ISyncExtension[], removed: IExtensionIdentifier[], updated: ISyncExtension[], remote: ISyncExtension[] | null } { const ignoredExtensions = this.configurationService.getValue('sync.ignoredExtensions') || []; // First time sync if (!remoteExtensions) { @@ -188,6 +194,7 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser const remoteExtensionsMap = remoteExtensions.reduce(addExtensionToMap, new Map()); const newRemoteExtensionsMap = remoteExtensions.reduce(addExtensionToMap, new Map()); const lastSyncExtensionsMap = lastSyncExtensions ? lastSyncExtensions.reduce(addExtensionToMap, new Map()) : null; + const skippedExtensionsMap = skippedExtensions.reduce(addExtensionToMap, new Map()); const ignoredExtensionsSet = ignoredExtensions.reduce((set, id) => { const uuid = uuids.get(id.toLowerCase()); return set.add(uuid ? `uuid:${uuid}` : `id:${id.toLowerCase()}`); @@ -274,8 +281,8 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser // Locally removed extensions for (const key of values(baseToLocal.removed)) { - // If not updated in remote - if (!baseToRemote.updated.has(key)) { + // If not skipped and not updated in remote + if (!skippedExtensionsMap.has(key) && !baseToRemote.updated.has(key)) { newRemoteExtensionsMap.delete(key); } } @@ -309,13 +316,17 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser return { added, removed, updated }; } - private async updateLocalExtensions(added: ISyncExtension[], removed: IExtensionIdentifier[], updated: ISyncExtension[]): Promise { + private async updateLocalExtensions(added: ISyncExtension[], removed: IExtensionIdentifier[], updated: ISyncExtension[], skippedExtensions: ISyncExtension[]): Promise { + const removeFromSkipped: IExtensionIdentifier[] = []; + const addToSkipped: ISyncExtension[] = []; + if (removed.length) { const installedExtensions = await this.extensionManagementService.getInstalled(ExtensionType.User); const extensionsToRemove = installedExtensions.filter(({ identifier }) => removed.some(r => areSameExtensions(identifier, r))); - await Promise.all(extensionsToRemove.map(e => { - this.logService.info('Extensions: Removing local extension.', e.identifier.id); - return this.extensionManagementService.uninstall(e); + await Promise.all(extensionsToRemove.map(async extensionToRemove => { + this.logService.info('Extensions: Removing local extension.', extensionToRemove.identifier.id); + await this.extensionManagementService.uninstall(extensionToRemove); + removeFromSkipped.push(extensionToRemove.identifier); })); } @@ -326,23 +337,39 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser this.logService.info('Extensions: Installing local extension.', e.identifier.id, extension.version); try { await this.extensionManagementService.installFromGallery(extension); - } catch (e) { - this.logService.error(e); + removeFromSkipped.push(extension.identifier); + } catch (error) { + addToSkipped.push(e); + this.logService.error(error); this.logService.info(localize('skip extension', "Skipping synchronising extension {0}", extension.displayName || extension.identifier.id)); } + } else { + addToSkipped.push(e); } })); } + + const newSkippedExtensions: ISyncExtension[] = []; + for (const skippedExtension of skippedExtensions) { + if (!removeFromSkipped.some(e => areSameExtensions(e, skippedExtension.identifier))) { + newSkippedExtensions.push(skippedExtension); + } + } + for (const skippedExtension of addToSkipped) { + if (!newSkippedExtensions.some(e => areSameExtensions(e.identifier, skippedExtension.identifier))) { + newSkippedExtensions.push(skippedExtension); + } + } + return newSkippedExtensions; } private async getLocalExtensions(): Promise { const installedExtensions = await this.extensionManagementService.getInstalled(ExtensionType.User); return installedExtensions - .filter(({ identifier }) => !!identifier.uuid) .map(({ identifier }) => ({ identifier, enabled: true })); } - private async getLastSyncUserData(): Promise { + private async getLastSyncUserData(): Promise { try { const content = await this.fileService.readFile(this.lastSyncExtensionsResource); return JSON.parse(content.value.toString()); @@ -351,14 +378,14 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser } } + private async updateLastSyncValue(lastSyncUserData: ILastSyncUserData): Promise { + await this.fileService.writeFile(this.lastSyncExtensionsResource, VSBuffer.fromString(JSON.stringify(lastSyncUserData))); + } + private async writeToRemote(extensions: ISyncExtension[], ref: string | null): Promise { const content = JSON.stringify(extensions); ref = await this.userDataSyncStoreService.write(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, content, ref); return { content, ref }; } - private async updateLastSyncValue(remoteUserData: IUserData): Promise { - await this.fileService.writeFile(this.lastSyncExtensionsResource, VSBuffer.fromString(JSON.stringify(remoteUserData))); - } - } From 0fae04b8a263cc5b8e8ec4c6a286bb57ae39d6c0 Mon Sep 17 00:00:00 2001 From: isidor Date: Thu, 5 Dec 2019 14:21:50 +0100 Subject: [PATCH 216/255] fixes #78384 --- test/automation/src/debug.ts | 2 +- test/smoke/src/areas/debug/debug.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/automation/src/debug.ts b/test/automation/src/debug.ts index 4739d8a9917..b4ea77a8f78 100644 --- a/test/automation/src/debug.ts +++ b/test/automation/src/debug.ts @@ -11,7 +11,7 @@ import { Editor } from './editor'; import { IElement } from '../src/driver'; const VIEWLET = 'div[id="workbench.view.debug"]'; -const DEBUG_VIEW = `${VIEWLET} .debug-view-content`; +const DEBUG_VIEW = `${VIEWLET}`; const CONFIGURE = `div[id="workbench.parts.sidebar"] .actions-container .codicon-gear`; const STOP = `.debug-toolbar .action-label[title*="Stop"]`; const STEP_OVER = `.debug-toolbar .action-label[title*="Step Over"]`; diff --git a/test/smoke/src/areas/debug/debug.test.ts b/test/smoke/src/areas/debug/debug.test.ts index 429fc7852d2..ee19ff9fd16 100644 --- a/test/smoke/src/areas/debug/debug.test.ts +++ b/test/smoke/src/areas/debug/debug.test.ts @@ -17,7 +17,7 @@ export function setup() { await app.workbench.debug.openDebugViewlet(); await app.workbench.quickopen.openFile('app.js'); - await app.workbench.debug.configure(); + await app.workbench.quickopen.runCommand('Debug: Open launch.json'); const launchJsonPath = path.join(app.workspacePathOrFolder, '.vscode', 'launch.json'); const content = fs.readFileSync(launchJsonPath, 'utf8'); From e038d4a6f93e676caca277d1c5a2b6752b59aac4 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Thu, 5 Dec 2019 14:50:31 +0100 Subject: [PATCH 217/255] More fixes to tokens adjusting --- src/vs/editor/common/model/tokensStore.ts | 25 ++++---- .../test/common/model/tokensStore.test.ts | 57 +++++++++++++++++-- 2 files changed, 66 insertions(+), 16 deletions(-) diff --git a/src/vs/editor/common/model/tokensStore.ts b/src/vs/editor/common/model/tokensStore.ts index 1df7dcdfb06..359c755e057 100644 --- a/src/vs/editor/common/model/tokensStore.ts +++ b/src/vs/editor/common/model/tokensStore.ts @@ -121,7 +121,7 @@ export interface IEncodedTokens { getMetadata(tokenIndex: number): number; clear(): void; - acceptDeleteRange(startDeltaLine: number, startCharacter: number, endDeltaLine: number, endCharacter: number): void; + acceptDeleteRange(horizontalShiftForFirstLineTokens: number, startDeltaLine: number, startCharacter: number, endDeltaLine: number, endCharacter: number): void; acceptInsertText(deltaLine: number, character: number, eolCount: number, firstLineLength: number, lastLineLength: number, firstCharCode: number): void; } @@ -173,7 +173,7 @@ export class SparseEncodedTokens implements IEncodedTokens { this._tokenCount = 0; } - public acceptDeleteRange(startDeltaLine: number, startCharacter: number, endDeltaLine: number, endCharacter: number): void { + public acceptDeleteRange(horizontalShiftForFirstLineTokens: number, startDeltaLine: number, startCharacter: number, endDeltaLine: number, endCharacter: number): void { // This is a bit complex, here are the cases I used to think about this: // // 1. The token starts before the deletion range @@ -292,14 +292,13 @@ export class SparseEncodedTokens implements IEncodedTokens { tokenDeltaLine -= deletedLineCount; } else if (tokenDeltaLine === endDeltaLine && tokenStartCharacter >= endCharacter) { // 4. (continued) The token starts after the deletion range, on the last line where a deletion occurs - tokenDeltaLine -= deletedLineCount; - if (deletedLineCount === 0) { - tokenStartCharacter -= (endCharacter - startCharacter); - tokenEndCharacter -= (endCharacter - startCharacter); - } else { - tokenStartCharacter -= endCharacter; - tokenEndCharacter -= endCharacter; + if (horizontalShiftForFirstLineTokens && tokenDeltaLine === 0) { + tokenStartCharacter += horizontalShiftForFirstLineTokens; + tokenEndCharacter += horizontalShiftForFirstLineTokens; } + tokenDeltaLine -= deletedLineCount; + tokenStartCharacter -= (endCharacter - startCharacter); + tokenEndCharacter -= (endCharacter - startCharacter); } else { throw new Error(`Not possible!`); } @@ -378,8 +377,8 @@ export class SparseEncodedTokens implements IEncodedTokens { } } // => the token must move and keep its size constant - tokenDeltaLine += eolCount; if (tokenDeltaLine === deltaLine) { + tokenDeltaLine += eolCount; // this token is on the line where the insertion is taking place if (eolCount === 0) { tokenStartCharacter += firstLineLength; @@ -389,6 +388,8 @@ export class SparseEncodedTokens implements IEncodedTokens { tokenStartCharacter = lastLineLength + (tokenStartCharacter - character); tokenEndCharacter = tokenStartCharacter + tokenLength; } + } else { + tokenDeltaLine += eolCount; } } @@ -532,9 +533,9 @@ export class MultilineTokens2 { const deletedBefore = -firstLineIndex; this.startLineNumber -= deletedBefore; - this.tokens.acceptDeleteRange(0, 0, lastLineIndex, range.endColumn - 1); + this.tokens.acceptDeleteRange(range.startColumn - 1, 0, 0, lastLineIndex, range.endColumn - 1); } else { - this.tokens.acceptDeleteRange(firstLineIndex, range.startColumn - 1, lastLineIndex, range.endColumn - 1); + this.tokens.acceptDeleteRange(0, firstLineIndex, range.startColumn - 1, lastLineIndex, range.endColumn - 1); } } diff --git a/src/vs/editor/test/common/model/tokensStore.test.ts b/src/vs/editor/test/common/model/tokensStore.test.ts index a7405ea9ab6..4413caeebc6 100644 --- a/src/vs/editor/test/common/model/tokensStore.test.ts +++ b/src/vs/editor/test/common/model/tokensStore.test.ts @@ -14,9 +14,10 @@ suite('TokensStore', () => { const SEMANTIC_COLOR = 5; - function parseTokensState(state: string[]): { text: string; tokens: number[]; } { + function parseTokensState(state: string[]): { text: string; tokens: MultilineTokens2; } { let text: string[] = []; let tokens: number[] = []; + let baseLine = 1; for (let i = 0; i < state.length; i++) { const line = state[i]; @@ -43,7 +44,10 @@ suite('TokensStore', () => { const tokenLength = secondPipeOffset - firstPipeOffset - 1; const metadata = (SEMANTIC_COLOR << MetadataConsts.FOREGROUND_OFFSET); - tokens.push(i, tokenStartCharacter, tokenStartCharacter + tokenLength, metadata); + if (tokens.length === 0) { + baseLine = i + 1; + } + tokens.push(i + 1 - baseLine, tokenStartCharacter, tokenStartCharacter + tokenLength, metadata); lineText += line.substr(firstPipeOffset + 1, tokenLength); startOffset = secondPipeOffset + 1; @@ -56,7 +60,7 @@ suite('TokensStore', () => { return { text: text.join('\n'), - tokens: tokens + tokens: new MultilineTokens2(baseLine, new SparseEncodedTokens(new Uint32Array(tokens))) }; } @@ -90,7 +94,7 @@ suite('TokensStore', () => { function testTokensAdjustment(rawInitialState: string[], edits: IIdentifiedSingleEditOperation[], rawFinalState: string[]) { const initialState = parseTokensState(rawInitialState); const model = TextModel.createFromString(initialState.text); - model.setSemanticTokens([new MultilineTokens2(1, new SparseEncodedTokens(new Uint32Array(initialState.tokens)))]); + model.setSemanticTokens([initialState.tokens]); model.applyEdits(edits); @@ -116,4 +120,49 @@ suite('TokensStore', () => { ); }); + test('deleting a newline', () => { + testTokensAdjustment( + [ + `import { |URI| } from 'vs/base/common/uri';`, + `const foo = |URI|.parse('hey');` + ], + [ + { range: new Range(1, 42, 2, 1), text: '' } + ], + [ + `import { |URI| } from 'vs/base/common/uri';const foo = |URI|.parse('hey');` + ] + ); + }); + + test('inserting a newline', () => { + testTokensAdjustment( + [ + `import { |URI| } from 'vs/base/common/uri';const foo = |URI|.parse('hey');` + ], + [ + { range: new Range(1, 42, 1, 42), text: '\n' } + ], + [ + `import { |URI| } from 'vs/base/common/uri';`, + `const foo = |URI|.parse('hey');` + ] + ); + }); + + test('deleting a newline 2', () => { + testTokensAdjustment( + [ + `import { `, + ` |URI| } from 'vs/base/common/uri';const foo = |URI|.parse('hey');` + ], + [ + { range: new Range(1, 10, 2, 5), text: '' } + ], + [ + `import { |URI| } from 'vs/base/common/uri';const foo = |URI|.parse('hey');` + ] + ); + }); + }); From 508950311e95263e2c48c0d52ef2bd5c8b74506c Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 5 Dec 2019 15:28:16 +0100 Subject: [PATCH 218/255] Fixes #83150: Handle case where the mouse is hitting the textarea when the textarea is rendered at the cursor --- .../editor/browser/controller/mouseHandler.ts | 13 ++++------ .../editor/browser/controller/mouseTarget.ts | 26 +++++++++++++------ .../browser/controller/textAreaHandler.ts | 23 +++++++++++++--- src/vs/editor/browser/view/viewImpl.ts | 7 +++-- 4 files changed, 48 insertions(+), 21 deletions(-) diff --git a/src/vs/editor/browser/controller/mouseHandler.ts b/src/vs/editor/browser/controller/mouseHandler.ts index d235e6ba791..6b5b7d372c7 100644 --- a/src/vs/editor/browser/controller/mouseHandler.ts +++ b/src/vs/editor/browser/controller/mouseHandler.ts @@ -9,11 +9,10 @@ import { StandardWheelEvent, IMouseWheelEvent } from 'vs/base/browser/mouseEvent import { RunOnceScheduler, TimeoutTimer } from 'vs/base/common/async'; import { Disposable } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; -import { HitTestContext, IViewZoneData, MouseTarget, MouseTargetFactory } from 'vs/editor/browser/controller/mouseTarget'; +import { HitTestContext, IViewZoneData, MouseTarget, MouseTargetFactory, PointerHandlerLastRenderData } from 'vs/editor/browser/controller/mouseTarget'; import * as editorBrowser from 'vs/editor/browser/editorBrowser'; import { ClientCoordinates, EditorMouseEvent, EditorMouseEventFactory, GlobalEditorMouseMoveMonitor, createEditorPagePosition } from 'vs/editor/browser/editorDom'; import { ViewController } from 'vs/editor/browser/view/viewController'; -import { IViewCursorRenderData } from 'vs/editor/browser/viewParts/viewCursors/viewCursor'; import { EditorZoom } from 'vs/editor/common/config/editorZoom'; import { Position } from 'vs/editor/common/core/position'; import { Selection } from 'vs/editor/common/core/selection'; @@ -46,9 +45,9 @@ export interface IPointerHandlerHelper { focusTextArea(): void; /** - * Get the last rendered information of the cursors. + * Get the last rendered information for cursors & textarea. */ - getLastViewCursorsRenderData(): IViewCursorRenderData[]; + getLastRenderData(): PointerHandlerLastRenderData; shouldSuppressMouseDownOnViewZone(viewZoneId: string): boolean; shouldSuppressMouseDownOnWidget(widgetId: string): boolean; @@ -158,13 +157,11 @@ export class MouseHandler extends ViewEventHandler { return null; } - const lastViewCursorsRenderData = this.viewHelper.getLastViewCursorsRenderData(); - return this.mouseTargetFactory.createMouseTarget(lastViewCursorsRenderData, editorPos, pos, null); + return this.mouseTargetFactory.createMouseTarget(this.viewHelper.getLastRenderData(), editorPos, pos, null); } protected _createMouseTarget(e: EditorMouseEvent, testEventTarget: boolean): editorBrowser.IMouseTarget { - const lastViewCursorsRenderData = this.viewHelper.getLastViewCursorsRenderData(); - return this.mouseTargetFactory.createMouseTarget(lastViewCursorsRenderData, e.editorPos, e.pos, testEventTarget ? e.target : null); + return this.mouseTargetFactory.createMouseTarget(this.viewHelper.getLastRenderData(), e.editorPos, e.pos, testEventTarget ? e.target : null); } private _getMouseColumn(e: EditorMouseEvent): number { diff --git a/src/vs/editor/browser/controller/mouseTarget.ts b/src/vs/editor/browser/controller/mouseTarget.ts index 1f737774248..384224093e6 100644 --- a/src/vs/editor/browser/controller/mouseTarget.ts +++ b/src/vs/editor/browser/controller/mouseTarget.ts @@ -89,6 +89,13 @@ interface IHitTestResult { hitTarget: Element | null; } +export class PointerHandlerLastRenderData { + constructor( + public readonly lastViewCursorsRenderData: IViewCursorRenderData[], + public readonly lastTextareaPosition: Position | null + ) { } +} + export class MouseTarget implements IMouseTarget { public readonly element: Element | null; @@ -232,19 +239,19 @@ export class HitTestContext { public readonly viewDomNode: HTMLElement; public readonly lineHeight: number; public readonly typicalHalfwidthCharacterWidth: number; - public readonly lastViewCursorsRenderData: IViewCursorRenderData[]; + public readonly lastRenderData: PointerHandlerLastRenderData; private readonly _context: ViewContext; private readonly _viewHelper: IPointerHandlerHelper; - constructor(context: ViewContext, viewHelper: IPointerHandlerHelper, lastViewCursorsRenderData: IViewCursorRenderData[]) { + constructor(context: ViewContext, viewHelper: IPointerHandlerHelper, lastRenderData: PointerHandlerLastRenderData) { this.model = context.model; const options = context.configuration.options; this.layoutInfo = options.get(EditorOption.layoutInfo); this.viewDomNode = viewHelper.viewDomNode; this.lineHeight = options.get(EditorOption.lineHeight); this.typicalHalfwidthCharacterWidth = options.get(EditorOption.fontInfo).typicalHalfwidthCharacterWidth; - this.lastViewCursorsRenderData = lastViewCursorsRenderData; + this.lastRenderData = lastRenderData; this._context = context; this._viewHelper = viewHelper; } @@ -462,8 +469,8 @@ export class MouseTargetFactory { return false; } - public createMouseTarget(lastViewCursorsRenderData: IViewCursorRenderData[], editorPos: EditorPagePosition, pos: PageCoordinates, target: HTMLElement | null): IMouseTarget { - const ctx = new HitTestContext(this._context, this._viewHelper, lastViewCursorsRenderData); + public createMouseTarget(lastRenderData: PointerHandlerLastRenderData, editorPos: EditorPagePosition, pos: PageCoordinates, target: HTMLElement | null): IMouseTarget { + const ctx = new HitTestContext(this._context, this._viewHelper, lastRenderData); const request = new HitTestRequest(ctx, editorPos, pos, target); try { const r = MouseTargetFactory._createMouseTarget(ctx, request, false); @@ -544,7 +551,7 @@ export class MouseTargetFactory { if (request.target) { // Check if we've hit a painted cursor - const lastViewCursorsRenderData = ctx.lastViewCursorsRenderData; + const lastViewCursorsRenderData = ctx.lastRenderData.lastViewCursorsRenderData; for (const d of lastViewCursorsRenderData) { @@ -560,7 +567,7 @@ export class MouseTargetFactory { // first or last rendered view line dom node, therefore help it out // and first check if we are on top of a cursor - const lastViewCursorsRenderData = ctx.lastViewCursorsRenderData; + const lastViewCursorsRenderData = ctx.lastRenderData.lastViewCursorsRenderData; const mouseContentHorizontalOffset = request.mouseContentHorizontalOffset; const mouseVerticalOffset = request.mouseVerticalOffset; @@ -602,7 +609,10 @@ export class MouseTargetFactory { private static _hitTestTextArea(ctx: HitTestContext, request: ResolvedHitTestRequest): MouseTarget | null { // Is it the textarea? if (ElementPath.isTextArea(request.targetPath)) { - return request.fulfill(MouseTargetType.TEXTAREA); + if (ctx.lastRenderData.lastTextareaPosition) { + return request.fulfill(MouseTargetType.CONTENT_TEXT, ctx.lastRenderData.lastTextareaPosition); + } + return request.fulfill(MouseTargetType.TEXTAREA, ctx.lastRenderData.lastTextareaPosition); } return null; } diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts index 4886c2e1e72..49ed53c81df 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/src/vs/editor/browser/controller/textAreaHandler.ts @@ -77,6 +77,12 @@ export class TextAreaHandler extends ViewPart { private _visibleTextArea: VisibleTextAreaData | null; private _selections: Selection[]; + /** + * The position at which the textarea was rendered. + * This is useful for hit-testing and determining the mouse position. + */ + private _lastRenderPosition: Position | null; + public readonly textArea: FastDomNode; public readonly textAreaCover: FastDomNode; private readonly _textAreaInput: TextAreaInput; @@ -104,6 +110,7 @@ export class TextAreaHandler extends ViewPart { this._visibleTextArea = null; this._selections = [new Selection(1, 1, 1, 1)]; + this._lastRenderPosition = null; // Text Area (The focus will always be in the textarea when the cursor is blinking) this.textArea = createFastDomNode(document.createElement('textarea')); @@ -413,13 +420,18 @@ export class TextAreaHandler extends ViewPart { this._textAreaInput.refreshFocusState(); } + public getLastRenderData(): Position | null { + return this._lastRenderPosition; + } + // --- end view API + private _primaryCursorPosition: Position = new Position(1, 1); private _primaryCursorVisibleRange: HorizontalPosition | null = null; public prepareRender(ctx: RenderingContext): void { - const primaryCursorPosition = new Position(this._selections[0].positionLineNumber, this._selections[0].positionColumn); - this._primaryCursorVisibleRange = ctx.visibleRangeForPosition(primaryCursorPosition); + this._primaryCursorPosition = new Position(this._selections[0].positionLineNumber, this._selections[0].positionColumn); + this._primaryCursorVisibleRange = ctx.visibleRangeForPosition(this._primaryCursorPosition); } public render(ctx: RestrictedRenderingContext): void { @@ -431,6 +443,7 @@ export class TextAreaHandler extends ViewPart { if (this._visibleTextArea) { // The text area is visible for composition reasons this._renderInsideEditor( + null, this._visibleTextArea.top - this._scrollTop, this._contentLeft + this._visibleTextArea.left - this._scrollLeft, this._visibleTextArea.width, @@ -465,6 +478,7 @@ export class TextAreaHandler extends ViewPart { // For the popup emoji input, we will make the text area as high as the line height // We will also make the fontSize and lineHeight the correct dimensions to help with the placement of these pickers this._renderInsideEditor( + this._primaryCursorPosition, top, left, canUseZeroSizeTextarea ? 0 : 1, this._lineHeight ); @@ -472,12 +486,14 @@ export class TextAreaHandler extends ViewPart { } this._renderInsideEditor( + this._primaryCursorPosition, top, left, canUseZeroSizeTextarea ? 0 : 1, canUseZeroSizeTextarea ? 0 : 1 ); } - private _renderInsideEditor(top: number, left: number, width: number, height: number): void { + private _renderInsideEditor(renderedPosition: Position | null, top: number, left: number, width: number, height: number): void { + this._lastRenderPosition = renderedPosition; const ta = this.textArea; const tac = this.textAreaCover; @@ -495,6 +511,7 @@ export class TextAreaHandler extends ViewPart { } private _renderAtTopLeft(): void { + this._lastRenderPosition = null; const ta = this.textArea; const tac = this.textAreaCover; diff --git a/src/vs/editor/browser/view/viewImpl.ts b/src/vs/editor/browser/view/viewImpl.ts index 02b337315c8..a79acee4e84 100644 --- a/src/vs/editor/browser/view/viewImpl.ts +++ b/src/vs/editor/browser/view/viewImpl.ts @@ -48,6 +48,7 @@ import { ViewEventHandler } from 'vs/editor/common/viewModel/viewEventHandler'; import { IViewModel } from 'vs/editor/common/viewModel/viewModel'; import { IThemeService, getThemeTypeSelector } from 'vs/platform/theme/common/themeService'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { PointerHandlerLastRenderData } from 'vs/editor/browser/controller/mouseTarget'; export interface IContentWidgetData { @@ -244,8 +245,10 @@ export class View extends ViewEventHandler { this.focus(); }, - getLastViewCursorsRenderData: () => { - return this.viewCursors.getLastRenderData() || []; + getLastRenderData: (): PointerHandlerLastRenderData => { + const lastViewCursorsRenderData = this.viewCursors.getLastRenderData() || []; + const lastTextareaPosition = this._textAreaHandler.getLastRenderData(); + return new PointerHandlerLastRenderData(lastViewCursorsRenderData, lastTextareaPosition); }, shouldSuppressMouseDownOnViewZone: (viewZoneId: string) => { return this.viewZones.shouldSuppressMouseDownOnViewZone(viewZoneId); From ac3fab27bdc63be0825faf52f2e27ee359cc8c14 Mon Sep 17 00:00:00 2001 From: isidor Date: Thu, 5 Dec 2019 16:11:40 +0100 Subject: [PATCH 219/255] fixes #86408 --- src/vs/workbench/contrib/files/browser/views/explorerView.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 956a7d9fd52..6a59c39f7d5 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -297,7 +297,10 @@ export class ExplorerView extends ViewletPane { for (const stat of this.tree.getSelection()) { const controller = this.renderer.getCompressedNavigationController(stat); - if (controller && focusedStat && controller === this.compressedNavigationController && stat !== focusedStat) { + if (controller && focusedStat && controller === this.compressedNavigationController) { + if (stat === focusedStat) { + selectedStats.push(stat); + } // Ignore stats which are selected but are part of the same compact node as the focused stat continue; } From 41e5936b45e07ec74cfe8a05c8a47dcb18a1105a Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Thu, 5 Dec 2019 16:12:31 +0100 Subject: [PATCH 220/255] Fixes #84281: Store bracket counts across embedded languages --- src/vs/editor/common/model/textModel.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index adea9b60c7d..ba78e7b90e0 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -2344,13 +2344,18 @@ export class TextModel extends Disposable implements model.ITextModel { public findEnclosingBrackets(_position: IPosition): [Range, Range] | null { const position = this.validatePosition(_position); const lineCount = this.getLineCount(); + const savedCounts = new Map(); let counts: number[] = []; - const resetCounts = (modeBrackets: RichEditBrackets | null) => { - counts = []; - for (let i = 0, len = modeBrackets ? modeBrackets.brackets.length : 0; i < len; i++) { - counts[i] = 0; + const resetCounts = (languageId: number, modeBrackets: RichEditBrackets | null) => { + if (!savedCounts.has(languageId)) { + let tmp = []; + for (let i = 0, len = modeBrackets ? modeBrackets.brackets.length : 0; i < len; i++) { + tmp[i] = 0; + } + savedCounts.set(languageId, tmp); } + counts = savedCounts.get(languageId)!; }; const searchInRange = (modeBrackets: RichEditBrackets, lineNumber: number, lineText: string, searchStartOffset: number, searchEndOffset: number): [Range, Range] | null => { while (true) { @@ -2396,7 +2401,7 @@ export class TextModel extends Disposable implements model.ITextModel { if (languageId !== tokenLanguageId) { languageId = tokenLanguageId; modeBrackets = LanguageConfigurationRegistry.getBracketsSupport(languageId); - resetCounts(modeBrackets); + resetCounts(languageId, modeBrackets); } } @@ -2415,7 +2420,7 @@ export class TextModel extends Disposable implements model.ITextModel { } languageId = tokenLanguageId; modeBrackets = LanguageConfigurationRegistry.getBracketsSupport(languageId); - resetCounts(modeBrackets); + resetCounts(languageId, modeBrackets); } const searchInToken = (!!modeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex))); From 67bbaaee9364955c74288760071704ccf9e19f4a Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 5 Dec 2019 17:12:37 +0100 Subject: [PATCH 221/255] Fixes #86133: Reverse resolved keybindings --- .../workbench/services/keybinding/browser/keybindingService.ts | 3 ++- .../keybinding/electron-browser/nativeKeymapService.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/services/keybinding/browser/keybindingService.ts b/src/vs/workbench/services/keybinding/browser/keybindingService.ts index 2c0becf3008..a952f48a57d 100644 --- a/src/vs/workbench/services/keybinding/browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/browser/keybindingService.ts @@ -338,7 +338,8 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { } const resolvedKeybindings = this.resolveKeybinding(keybinding); - for (const resolvedKeybinding of resolvedKeybindings) { + for (let i = resolvedKeybindings.length - 1; i >= 0; i--) { + const resolvedKeybinding = resolvedKeybindings[i]; result[resultLen++] = new ResolvedKeybindingItem(resolvedKeybinding, item.command, item.commandArgs, when, isDefault); } } diff --git a/src/vs/workbench/services/keybinding/electron-browser/nativeKeymapService.ts b/src/vs/workbench/services/keybinding/electron-browser/nativeKeymapService.ts index 757f632c3a7..6ef2edda43d 100644 --- a/src/vs/workbench/services/keybinding/electron-browser/nativeKeymapService.ts +++ b/src/vs/workbench/services/keybinding/electron-browser/nativeKeymapService.ts @@ -61,7 +61,7 @@ export class KeyboardMapperFactory { private static _isUSStandard(_kbInfo: nativeKeymap.IKeyboardLayoutInfo): boolean { if (OS === OperatingSystem.Linux) { const kbInfo = _kbInfo; - return (kbInfo && kbInfo.layout === 'us'); + return (kbInfo && (kbInfo.layout === 'us' || /^us,/.test(kbInfo.layout))); } if (OS === OperatingSystem.Macintosh) { From 440916469ce83f6d1f8cf8307b733abba049adae Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Thu, 5 Dec 2019 17:19:35 +0100 Subject: [PATCH 222/255] Fixes #86124 --- src/vs/vscode.proposed.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 251d6830ea3..9d69c83b1cb 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -68,7 +68,7 @@ declare module 'vscode' { //#endregion - //#region Alex - semantic tokens + //#region Semantic tokens: https://github.com/microsoft/vscode/issues/86415 export class SemanticTokensLegend { public readonly tokenTypes: string[]; From 57bb2ca49a494a8f1d5f7cf7c23d1b96f4fa1c7d Mon Sep 17 00:00:00 2001 From: isidor Date: Thu, 5 Dec 2019 17:27:41 +0100 Subject: [PATCH 223/255] fixes #84241 --- src/vs/workbench/contrib/files/common/explorerModel.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/files/common/explorerModel.ts b/src/vs/workbench/contrib/files/common/explorerModel.ts index cd74eea7b4e..dd17f19dfb6 100644 --- a/src/vs/workbench/contrib/files/common/explorerModel.ts +++ b/src/vs/workbench/contrib/files/common/explorerModel.ts @@ -197,7 +197,9 @@ export class ExplorerItem { // Properties local.resource = disk.resource; - local.updateName(disk.name); + if (!local.isRoot) { + local.updateName(disk.name); + } local._isDirectory = disk.isDirectory; local._mtime = disk.mtime; local._isDirectoryResolved = disk._isDirectoryResolved; From 4b394b938ec5f43fa3060746728df6ea460f49c2 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Thu, 5 Dec 2019 17:28:23 +0100 Subject: [PATCH 224/255] Fixes #86166 --- src/vs/vscode.proposed.d.ts | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 9d69c83b1cb..19a434638c0 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -84,6 +84,12 @@ declare module 'vscode' { } export class SemanticTokens { + /** + * The result id of the tokens. + * + * On a next call to `provideSemanticTokens`, if VS Code still holds in memory this result, + * the result id will be passed in as `SemanticTokensRequestOptions.previousResultId`. + */ readonly resultId?: string; readonly data: Uint32Array; @@ -91,6 +97,12 @@ declare module 'vscode' { } export class SemanticTokensEdits { + /** + * The result id of the tokens. + * + * On a next call to `provideSemanticTokens`, if VS Code still holds in memory this result, + * the result id will be passed in as `SemanticTokensRequestOptions.previousResultId`. + */ readonly resultId?: string; readonly edits: SemanticTokensEdit[]; @@ -107,6 +119,11 @@ declare module 'vscode' { export interface SemanticTokensRequestOptions { readonly ranges?: readonly Range[]; + /** + * The previous result id that the editor still holds in memory. + * + * Only when this is set it is safe for a `SemanticTokensProvider` to return `SemanticTokensEdits`. + */ readonly previousResultId?: string; } @@ -191,7 +208,13 @@ declare module 'vscode' { * [ 3,5,3,1,6, 0,5,4,2,0, 1,3,5,1,2, 2,2,7,3,0 ] * ``` * - * A smart tokens provider can compute a diff from the previous result to the new result + * A smart tokens provider can return a `resultId` to `SemanticTokens`. Then, if the editor still has in memory the previous + * result, the editor will pass in options the previous result id at `SemanticTokensRequestOptions.previousResultId`. Only when + * the editor passes in the previous result id, it is safe and smart for a smart tokens provider can compute a diff from the + * previous result to the new result. + * + * *NOTE*: It is illegal to return `SemanticTokensEdits` if `options.previousResultId` is not set! + * * ``` * [ 2,5,3,1,6, 0,5,4,2,0, 3,2,7,3,0 ] * [ 3,5,3,1,6, 0,5,4,2,0, 1,3,5,1,2, 2,2,7,3,0 ] From c8eb6fd4aa8ff767922f89a372235dae13ecb766 Mon Sep 17 00:00:00 2001 From: isidor Date: Thu, 5 Dec 2019 20:40:11 +0100 Subject: [PATCH 225/255] polish debug console arrows fixes #86016 --- src/vs/workbench/contrib/debug/browser/media/repl.css | 7 +++++-- src/vs/workbench/contrib/debug/browser/repl.ts | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/media/repl.css b/src/vs/workbench/contrib/debug/browser/media/repl.css index 96b7e52d65d..2b38626d95d 100644 --- a/src/vs/workbench/contrib/debug/browser/media/repl.css +++ b/src/vs/workbench/contrib/debug/browser/media/repl.css @@ -30,6 +30,9 @@ cursor: pointer; } +.monaco-workbench.mac .repl .repl-tree .monaco-tl-twistie { + padding-right: 0px; +} .repl .repl-tree .output.expression.value-and-source { display: flex; @@ -42,11 +45,11 @@ .repl .repl-tree .monaco-tl-contents .arrow { position:absolute; left: 2px; - opacity: 0.3; + opacity: 0.25; } .vs-dark .repl .repl-tree .monaco-tl-contents .arrow { - opacity: 0.45; + opacity: 0.4; } .repl .repl-tree .output.expression.value-and-source .source { diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index df3ba1ceef0..935a160bfa0 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -610,7 +610,7 @@ class ReplEvaluationInputsRenderer implements ITreeRenderer Date: Thu, 5 Dec 2019 11:42:37 -0800 Subject: [PATCH 226/255] Fix #86058 --- .../html-language-features/client/src/htmlMain.ts | 4 ++-- .../client/src/{matchingTag.ts => mirrorCursor.ts} | 13 +++++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) rename extensions/html-language-features/client/src/{matchingTag.ts => mirrorCursor.ts} (95%) diff --git a/extensions/html-language-features/client/src/htmlMain.ts b/extensions/html-language-features/client/src/htmlMain.ts index 9a4d392ff28..e8a9a2fa22b 100644 --- a/extensions/html-language-features/client/src/htmlMain.ts +++ b/extensions/html-language-features/client/src/htmlMain.ts @@ -14,7 +14,7 @@ import { EMPTY_ELEMENTS } from './htmlEmptyTagsShared'; import { activateTagClosing } from './tagClosing'; import TelemetryReporter from 'vscode-extension-telemetry'; import { getCustomDataPathsInAllWorkspaces, getCustomDataPathsFromAllExtensions } from './customData'; -import { activateMatchingTagPosition as activateMatchingTagSelection } from './matchingTag'; +import { activateMirrorCursor } from './mirrorCursor'; namespace TagCloseRequest { export const type: RequestType = new RequestType('html/tag'); @@ -118,7 +118,7 @@ export function activate(context: ExtensionContext) { return client.sendRequest(MatchingTagPositionRequest.type, param); }; - disposable = activateMatchingTagSelection(matchingTagPositionRequestor, { html: true, handlebars: true }, 'html.mirrorCursorOnMatchingTag'); + disposable = activateMirrorCursor(matchingTagPositionRequestor, { html: true, handlebars: true }, 'html.mirrorCursorOnMatchingTag'); toDispose.push(disposable); disposable = client.onTelemetry(e => { diff --git a/extensions/html-language-features/client/src/matchingTag.ts b/extensions/html-language-features/client/src/mirrorCursor.ts similarity index 95% rename from extensions/html-language-features/client/src/matchingTag.ts rename to extensions/html-language-features/client/src/mirrorCursor.ts index d12726d35da..041c357032b 100644 --- a/extensions/html-language-features/client/src/matchingTag.ts +++ b/extensions/html-language-features/client/src/mirrorCursor.ts @@ -15,7 +15,7 @@ import { WorkspaceEdit } from 'vscode'; -export function activateMatchingTagPosition( +export function activateMirrorCursor( matchingTagPositionProvider: (document: TextDocument, position: Position) => Thenable, supportedLanguages: { [id: string]: boolean }, configName: string @@ -173,10 +173,19 @@ function shouldDoCleanupForHtmlAttributeInput(document: TextDocument, firstPos: const primaryBeforeSecondary = document.offsetAt(firstPos) < document.offsetAt(secondPos); + /** + * Check two cases + *
+ *
+ * Before 1st cursor: ` ` + * After 1st cursor: `>` or ` ` + * Before 2nd cursor: ` ` + * After 2nd cursor: `>` + */ return ( primaryBeforeSecondary && charBeforePrimarySelection === ' ' && - charAfterPrimarySelection === '>' && + (charAfterPrimarySelection === '>' || charAfterPrimarySelection === ' ') && charBeforeSecondarySelection === ' ' && charAfterSecondarySelection === '>' ); From fea4d4697cdbb85bc0b0a5b1b11111d50d048d51 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 5 Dec 2019 11:46:56 -0800 Subject: [PATCH 227/255] Bump node2 for #86411 --- build/builtInExtensions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/builtInExtensions.json b/build/builtInExtensions.json index 3303d285507..68d891d09d4 100644 --- a/build/builtInExtensions.json +++ b/build/builtInExtensions.json @@ -16,7 +16,7 @@ }, { "name": "ms-vscode.node-debug2", - "version": "1.41.2", + "version": "1.41.3", "repo": "https://github.com/Microsoft/vscode-node-debug2", "metadata": { "id": "36d19e17-7569-4841-a001-947eb18602b2", From 4c20f308e37e6079253bab065ede7911e0f164a8 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Thu, 5 Dec 2019 14:02:07 -0800 Subject: [PATCH 228/255] Allow root of grid to contain single node group fixes #85863 --- src/vs/base/browser/ui/grid/grid.ts | 8 ++++---- src/vs/base/browser/ui/grid/gridview.ts | 2 ++ src/vs/base/test/browser/ui/grid/grid.test.ts | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/vs/base/browser/ui/grid/grid.ts b/src/vs/base/browser/ui/grid/grid.ts index 14bda5075db..336f2ff4a21 100644 --- a/src/vs/base/browser/ui/grid/grid.ts +++ b/src/vs/base/browser/ui/grid/grid.ts @@ -604,8 +604,8 @@ export class SerializableGrid extends Grid { export type GridNodeDescriptor = { size?: number, groups?: GridNodeDescriptor[] }; export type GridDescriptor = { orientation: Orientation, groups?: GridNodeDescriptor[] }; -export function sanitizeGridNodeDescriptor(nodeDescriptor: GridNodeDescriptor): void { - if (nodeDescriptor.groups && nodeDescriptor.groups.length <= 1) { +export function sanitizeGridNodeDescriptor(nodeDescriptor: GridNodeDescriptor, rootNode: boolean): void { + if (!rootNode && nodeDescriptor.groups && nodeDescriptor.groups.length <= 1) { nodeDescriptor.groups = undefined; } @@ -617,7 +617,7 @@ export function sanitizeGridNodeDescriptor(nodeDescriptor: GridNodeDescriptor): let totalDefinedSizeCount = 0; for (const child of nodeDescriptor.groups) { - sanitizeGridNodeDescriptor(child); + sanitizeGridNodeDescriptor(child, false); if (child.size) { totalDefinedSize += child.size; @@ -665,7 +665,7 @@ function getDimensions(node: ISerializedNode, orientation: Orientation): { width } export function createSerializedGrid(gridDescriptor: GridDescriptor): ISerializedGrid { - sanitizeGridNodeDescriptor(gridDescriptor); + sanitizeGridNodeDescriptor(gridDescriptor, true); const root = createSerializedNode(gridDescriptor); const { width, height } = getDimensions(root, gridDescriptor.orientation); diff --git a/src/vs/base/browser/ui/grid/gridview.ts b/src/vs/base/browser/ui/grid/gridview.ts index 873979ce4b4..88716fd28d3 100644 --- a/src/vs/base/browser/ui/grid/gridview.ts +++ b/src/vs/base/browser/ui/grid/gridview.ts @@ -1086,6 +1086,8 @@ export class GridView implements IDisposable { throw new Error('Invalid JSON: \'width\' property must be a number.'); } else if (typeof json.height !== 'number') { throw new Error('Invalid JSON: \'height\' property must be a number.'); + } else if (json.root?.type !== 'branch') { + throw new Error('Invalid JSON: \'root\' property must have \'type\' value of branch.'); } const orientation = json.orientation; diff --git a/src/vs/base/test/browser/ui/grid/grid.test.ts b/src/vs/base/test/browser/ui/grid/grid.test.ts index ae7093bcadc..7956239d44d 100644 --- a/src/vs/base/test/browser/ui/grid/grid.test.ts +++ b/src/vs/base/test/browser/ui/grid/grid.test.ts @@ -787,7 +787,7 @@ suite('SerializableGrid', function () { test('sanitizeGridNodeDescriptor', () => { const nodeDescriptor = { groups: [{ size: 0.2 }, { size: 0.2 }, { size: 0.6, groups: [{}, {}] }] }; const nodeDescriptorCopy = deepClone(nodeDescriptor); - sanitizeGridNodeDescriptor(nodeDescriptorCopy); + sanitizeGridNodeDescriptor(nodeDescriptorCopy, true); assert.deepEqual(nodeDescriptorCopy, { groups: [{ size: 0.2 }, { size: 0.2 }, { size: 0.6, groups: [{ size: 0.5 }, { size: 0.5 }] }] }); }); From 1beea2f864c1f403ab225dbadfdc398d76faef94 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Thu, 5 Dec 2019 14:04:06 -0800 Subject: [PATCH 229/255] Fix #86118 for Windows --- src/vs/workbench/contrib/search/browser/searchEditor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/search/browser/searchEditor.ts b/src/vs/workbench/contrib/search/browser/searchEditor.ts index 9b54955af67..4eee6a2d7fb 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/search/browser/searchEditor.ts @@ -47,7 +47,7 @@ const matchToSearchResultFormat = (match: Match): { line: string, ranges: Range[ const prefix = ` ${lineNumber}: ${paddingStr}`; const prefixOffset = prefix.length; - const line = (prefix + sourceLine); + const line = (prefix + sourceLine).replace(/\r?\n?$/, ''); const rangeOnThisLine = ({ start, end }: { start?: number; end?: number; }) => new Range(1, (start ?? 1) + prefixOffset, 1, (end ?? sourceLine.length + 1) + prefixOffset); From e7c13723938e42ee5ac402289d7b59be9a727536 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 5 Dec 2019 14:44:59 -0800 Subject: [PATCH 230/255] lcd - revert layer promotion for now (#86396) * lcd - revert layer promotion for now * Revert "Fix #86347. Fixed overflow widget only for extension viewlet" This reverts commit beb8a0c7b7a1a383181c7bab6f99f007e0629d43. * Revert "Re #85313. Fix suggest widget position for extension editor." This reverts commit 9d47191895b0705f689a234c1a4e1e790303cb70. * repatch titlebar for AA fix --- src/vs/workbench/browser/media/part.css | 5 --- .../activitybar/media/activitybarpart.css | 16 ---------- .../parts/editor/media/editorgroupview.css | 14 --------- .../browser/parts/panel/media/panelpart.css | 16 +--------- .../parts/sidebar/media/sidebarpart.css | 16 ++-------- .../parts/statusbar/media/statusbarpart.css | 14 --------- .../parts/titlebar/media/titlebarpart.css | 31 +++++-------------- .../codeEditor/browser/simpleEditorOptions.ts | 4 +-- .../suggestEnabledInput.ts | 3 +- .../extensions/browser/extensionsViewlet.ts | 2 +- .../preferences/browser/settingsEditor2.ts | 1 - 11 files changed, 14 insertions(+), 108 deletions(-) diff --git a/src/vs/workbench/browser/media/part.css b/src/vs/workbench/browser/media/part.css index d71cf8f50d1..89dd7fe4c9b 100644 --- a/src/vs/workbench/browser/media/part.css +++ b/src/vs/workbench/browser/media/part.css @@ -8,11 +8,6 @@ overflow: hidden; } -.monaco-workbench.windows.chromium .part:focus-within, -.monaco-workbench.linux.chromium .part:focus-within { - z-index: 500; -} - .monaco-workbench .part > .title { display: none; /* Parts have to opt in to show title area */ } diff --git a/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css b/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css index 4dc487ae5d0..cbc3d14705b 100644 --- a/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css +++ b/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css @@ -7,22 +7,6 @@ width: 48px; } -.monaco-workbench.windows.chromium .part.activitybar, -.monaco-workbench.linux.chromium .part.activitybar { - /* - * Explicitly put the part onto its own layer to help Chrome to - * render the content with LCD-anti-aliasing. By partioning the - * workbench into multiple layers, we can ensure that a bad - * behaving part is not making another part fallback to greyscale - * rendering. - * - * macOS: does not render LCD-anti-aliased. - */ - transform: translate3d(0px, 0px, 0px); - overflow: visible; /* when a new layer is created, we need to set overflow visible to avoid clipping the menubar */ - position: relative; -} - .monaco-workbench .activitybar > .content { height: 100%; display: flex; diff --git a/src/vs/workbench/browser/parts/editor/media/editorgroupview.css b/src/vs/workbench/browser/parts/editor/media/editorgroupview.css index c4c66c12eaa..582eac233e2 100644 --- a/src/vs/workbench/browser/parts/editor/media/editorgroupview.css +++ b/src/vs/workbench/browser/parts/editor/media/editorgroupview.css @@ -48,20 +48,6 @@ overflow: hidden; } -.monaco-workbench.windows.chromium .part.editor > .content .editor-group-container > .title, -.monaco-workbench.linux.chromium .part.editor > .content .editor-group-container > .title { - /* - * Explicitly put the part onto its own layer to help Chrome to - * render the content with LCD-anti-aliasing. By partioning the - * workbench into multiple layers, we can ensure that a bad - * behaving part is not making another part fallback to greyscale - * rendering. - * - * macOS: does not render LCD-anti-aliased. - */ - transform: translate3d(0px, 0px, 0px); -} - .monaco-workbench .part.editor > .content .editor-group-container > .title:not(.tabs) { display: flex; /* when tabs are not shown, use flex layout */ flex-wrap: nowrap; diff --git a/src/vs/workbench/browser/parts/panel/media/panelpart.css b/src/vs/workbench/browser/parts/panel/media/panelpart.css index 5db9c19a1ea..4909d7e9738 100644 --- a/src/vs/workbench/browser/parts/panel/media/panelpart.css +++ b/src/vs/workbench/browser/parts/panel/media/panelpart.css @@ -12,20 +12,6 @@ z-index: initial; } -.monaco-workbench.windows.chromium .part.panel, -.monaco-workbench.linux.chromium .part.panel { - /* - * Explicitly put the part onto its own layer to help Chrome to - * render the content with LCD-anti-aliasing. By partioning the - * workbench into multiple layers, we can ensure that a bad - * behaving part is not making another part fallback to greyscale - * rendering. - * - * macOS: does not render LCD-anti-aliased. - */ - transform: translate3d(0px, 0px, 0px); -} - .monaco-workbench .part.panel .title { height: 35px; display: flex; @@ -114,7 +100,7 @@ text-align: center; display: inline-block; box-sizing: border-box; -} + } /** Actions */ diff --git a/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css b/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css index ac2845f9c4b..25b9dcd0625 100644 --- a/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css +++ b/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css @@ -3,20 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-workbench.windows.chromium .part.sidebar, -.monaco-workbench.linux.chromium .part.sidebar { - /* - * Explicitly put the part onto its own layer to help Chrome to - * render the content with LCD-anti-aliasing. By partioning the - * workbench into multiple layers, we can ensure that a bad - * behaving part is not making another part fallback to greyscale - * rendering. - * - * macOS: does not render LCD-anti-aliased. - */ - transform: translate3d(0px, 0px, 0px); - overflow: visible; - position: relative; +.monaco-workbench .sidebar > .content { + overflow: hidden; } .monaco-workbench.nosidebar > .part.sidebar { diff --git a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css index 560bb371cbf..3102b114c52 100644 --- a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css +++ b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css @@ -13,20 +13,6 @@ overflow: visible; } -.monaco-workbench.windows.chromium .part.statusbar, -.monaco-workbench.linux.chromium .part.statusbar { - /* - * Explicitly put the part onto its own layer to help Chrome to - * render the content with LCD-anti-aliasing. By partioning the - * workbench into multiple layers, we can ensure that a bad - * behaving part is not making another part fallback to greyscale - * rendering. - * - * macOS: does not render LCD-anti-aliased. - */ - transform: translate3d(0px, 0px, 0px); -} - .monaco-workbench .part.statusbar.status-border-top::after { content: ''; position: absolute; diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index e71467abfc9..2725a7d5da5 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -19,22 +19,6 @@ display: flex; } -.monaco-workbench.windows .part.titlebar, -.monaco-workbench.linux .part.titlebar { - /* - * Explicitly put the part onto its own layer to help Chrome to - * render the content with LCD-anti-aliasing. By partioning the - * workbench into multiple layers, we can ensure that a bad - * behaving part is not making another part fallback to greyscale - * rendering. - * - * macOS: does not render LCD-anti-aliased. - */ - transform: translate3d(0px, 0px, 0px); - position: relative; - z-index: 1500 !important; /* move the entire titlebar above the workbench, except modals/dialogs */ -} - .monaco-workbench .part.titlebar > .titlebar-drag-region { top: 0; left: 0; @@ -45,11 +29,6 @@ -webkit-app-region: drag; } -.monaco-workbench .part.titlebar > .menubar { - /* Move above drag region since negative z-index on that element causes AA issues */ - z-index: 1; -} - .monaco-workbench .part.titlebar > .window-title { flex: 0 1 auto; font-size: 12px; @@ -78,6 +57,11 @@ cursor: default; } +.monaco-workbench .part.titlebar > .menubar { + /* move menubar above drag region as negative z-index on drag region cause greyscale AA */ + z-index: 2000; +} + .monaco-workbench.linux .part.titlebar > .window-title { font-size: inherit; } @@ -100,7 +84,7 @@ width: 35px; height: 100%; position: relative; - z-index: 2; /* highest level of titlebar */ + z-index: 3000; background-image: url('code-icon.svg'); background-repeat: no-repeat; background-position: center center; @@ -118,12 +102,11 @@ flex-shrink: 0; text-align: center; position: relative; - z-index: 2; /* highest level of titlebar */ + z-index: 3000; -webkit-app-region: no-drag; height: 100%; width: 138px; margin-left: auto; - transform: translate3d(0px, 0px, 0px); } .monaco-workbench.fullscreen .part.titlebar > .window-controls-container { diff --git a/src/vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts b/src/vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts index ff80bd1ec20..ac40c376e27 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts @@ -13,7 +13,7 @@ import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEdito import { TabCompletionController } from 'vs/workbench/contrib/snippets/browser/tabCompletion'; import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; -export function getSimpleEditorOptions(fixedOverflowWidgets: boolean = true): IEditorOptions { +export function getSimpleEditorOptions(): IEditorOptions { return { wordWrap: 'on', overviewRulerLanes: 0, @@ -30,7 +30,7 @@ export function getSimpleEditorOptions(fixedOverflowWidgets: boolean = true): IE overviewRulerBorder: false, scrollBeyondLastLine: false, renderLineHighlight: 'none', - fixedOverflowWidgets: fixedOverflowWidgets, + fixedOverflowWidgets: true, acceptSuggestionOnEnter: 'smart', minimap: { enabled: false diff --git a/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts index ee09a61a8e6..b9731888148 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts @@ -72,7 +72,6 @@ interface SuggestEnabledInputOptions { * Context key tracking the focus state of this element */ focusContextKey?: IContextKey; - fixedOverflowWidgets?: boolean; } export interface ISuggestEnabledInputStyleOverrides extends IStyleOverrides { @@ -127,7 +126,7 @@ export class SuggestEnabledInput extends Widget implements IThemable { this.placeholderText = append(this.stylingContainer, $('.suggest-input-placeholder', undefined, options.placeholderText || '')); const editorOptions: IEditorOptions = mixin( - getSimpleEditorOptions(!!options.fixedOverflowWidgets), + getSimpleEditorOptions(), getSuggestEnabledInputOptions(ariaLabel)); this.inputWidget = instantiationService.createInstance(CodeEditorWidget, this.stylingContainer, diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index 669297557d5..4ac7d34337e 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -405,7 +405,7 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio else { return 'd'; } }, provideResults: (query: string) => Query.suggestions(query) - }, placeholder, 'extensions:searchinput', { placeholderText: placeholder, value: searchValue, fixedOverflowWidgets: false })); + }, placeholder, 'extensions:searchinput', { placeholderText: placeholder, value: searchValue })); if (this.searchBox.getValue()) { this.triggerSearch(); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index e339ce2784e..09467700dd5 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -406,7 +406,6 @@ export class SettingsEditor2 extends BaseEditor { }, searchBoxLabel, 'settingseditor:searchinput' + SettingsEditor2.NUM_INSTANCES++, { placeholderText: searchBoxLabel, focusContextKey: this.searchFocusContextKey, - fixedOverflowWidgets: true // TODO: Aria-live }) ); From 9752af2c5cd393281699c3f2636d087ecd7b7580 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 6 Dec 2019 00:05:43 +0100 Subject: [PATCH 231/255] Fixes #84595: editor.matchBrackets can now be 'never' | 'near' | 'always' --- .../editor/common/config/commonEditorConfig.ts | 7 +++++++ src/vs/editor/common/config/editorOptions.ts | 12 +++++++----- .../contrib/bracketMatching/bracketMatching.ts | 17 +++++++++-------- src/vs/monaco.d.ts | 4 ++-- 4 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index 994f3e4f6c9..f9f34b493f8 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -263,6 +263,13 @@ function migrateOptions(options: IEditorOptions): void { } else if (autoIndent === false) { options.autoIndent = 'advanced'; } + + const matchBrackets = options.matchBrackets; + if (matchBrackets === true) { + options.matchBrackets = 'always'; + } else if (matchBrackets === false) { + options.matchBrackets = 'never'; + } } function deepCloneAndMigrateOptions(_options: IEditorOptions): IEditorOptions { diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 7ad5563ff4a..74662b49749 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -481,9 +481,9 @@ export interface IEditorOptions { showFoldingControls?: 'always' | 'mouseover'; /** * Enable highlighting of matching brackets. - * Defaults to true. + * Defaults to 'always'. */ - matchBrackets?: boolean; + matchBrackets?: 'never' | 'near' | 'always'; /** * Enable rendering of whitespace. * Defaults to none. @@ -3356,9 +3356,11 @@ export const EditorOptions = { EditorOption.links, 'links', true, { description: nls.localize('links', "Controls whether the editor should detect links and make them clickable.") } )), - matchBrackets: register(new EditorBooleanOption( - EditorOption.matchBrackets, 'matchBrackets', true, - { description: nls.localize('matchBrackets', "Highlight matching brackets when one of them is selected.") } + matchBrackets: register(new EditorStringEnumOption( + EditorOption.matchBrackets, 'matchBrackets', + 'always' as 'never' | 'near' | 'always', + ['always', 'near', 'never'] as const, + { description: nls.localize('matchBrackets', "Highlight matching brackets.") } )), minimap: register(new EditorMinimap()), mouseStyle: register(new EditorStringEnumOption( diff --git a/src/vs/editor/contrib/bracketMatching/bracketMatching.ts b/src/vs/editor/contrib/bracketMatching/bracketMatching.ts index ac8db2f7325..2bb287315ee 100644 --- a/src/vs/editor/contrib/bracketMatching/bracketMatching.ts +++ b/src/vs/editor/contrib/bracketMatching/bracketMatching.ts @@ -116,7 +116,7 @@ export class BracketMatchingController extends Disposable implements editorCommo private _lastVersionId: number; private _decorations: string[]; private readonly _updateBracketsSoon: RunOnceScheduler; - private _matchBrackets: boolean; + private _matchBrackets: 'never' | 'near' | 'always'; constructor( editor: ICodeEditor @@ -132,7 +132,7 @@ export class BracketMatchingController extends Disposable implements editorCommo this._updateBracketsSoon.schedule(); this._register(editor.onDidChangeCursorPosition((e) => { - if (!this._matchBrackets) { + if (this._matchBrackets === 'never') { // Early exit if nothing needs to be done! // Leave some form of early exit check here if you wish to continue being a cursor position change listener ;) return; @@ -153,12 +153,13 @@ export class BracketMatchingController extends Disposable implements editorCommo this._updateBracketsSoon.schedule(); })); this._register(editor.onDidChangeConfiguration((e) => { - this._matchBrackets = this._editor.getOption(EditorOption.matchBrackets); - if (!this._matchBrackets && this._decorations.length > 0) { - // Remove existing decorations if bracket matching is off + if (e.hasChanged(EditorOption.matchBrackets)) { + this._matchBrackets = this._editor.getOption(EditorOption.matchBrackets); this._decorations = this._editor.deltaDecorations(this._decorations, []); + this._lastBracketsData = []; + this._lastVersionId = 0; + this._updateBracketsSoon.schedule(); } - this._updateBracketsSoon.schedule(); })); } @@ -262,7 +263,7 @@ export class BracketMatchingController extends Disposable implements editorCommo }); private _updateBrackets(): void { - if (!this._matchBrackets) { + if (this._matchBrackets === 'never') { return; } this._recomputeBrackets(); @@ -332,7 +333,7 @@ export class BracketMatchingController extends Disposable implements editorCommo } else { let brackets = model.matchBracket(position); let options = BracketMatchingController._DECORATION_OPTIONS_WITH_OVERVIEW_RULER; - if (!brackets) { + if (!brackets && this._matchBrackets === 'always') { brackets = model.findEnclosingBrackets(position); options = BracketMatchingController._DECORATION_OPTIONS_WITHOUT_OVERVIEW_RULER; } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 6f20419c1f3..100626ba2f1 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -2880,9 +2880,9 @@ declare namespace monaco.editor { showFoldingControls?: 'always' | 'mouseover'; /** * Enable highlighting of matching brackets. - * Defaults to true. + * Defaults to 'always'. */ - matchBrackets?: boolean; + matchBrackets?: 'never' | 'near' | 'always'; /** * Enable rendering of whitespace. * Defaults to none. From 9bdb4a2f700bc8d9846315acbf25db150c9f4d82 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 5 Dec 2019 15:05:35 -0800 Subject: [PATCH 232/255] Handle normalized windows paths in resource map Fixes #86433 During path normalization, we convert `\` in windows paths to `/`. This causes the isWindowsPath check to fail I think it is generally safe to assume that file paths that start with a drive letter and then any type of slash should be treated as windows paths --- .../typescript-language-features/src/utils/resourceMap.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/typescript-language-features/src/utils/resourceMap.ts b/extensions/typescript-language-features/src/utils/resourceMap.ts index a10fbd91636..e942fae4583 100644 --- a/extensions/typescript-language-features/src/utils/resourceMap.ts +++ b/extensions/typescript-language-features/src/utils/resourceMap.ts @@ -101,5 +101,5 @@ export class ResourceMap { } export function isWindowsPath(path: string): boolean { - return /^[a-zA-Z]:\\/.test(path); + return /^[a-zA-Z]:[\/\\]/.test(path); } From bc2fbe2893daf00b0b3ef9bd6ff326a5ddfd933f Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Thu, 5 Dec 2019 15:15:39 -0800 Subject: [PATCH 233/255] only suppress mouse down for inline diff view. --- src/vs/editor/browser/widget/diffEditorWidget.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index 9dbc302244e..91b91732033 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -128,12 +128,13 @@ class VisualEditorState { this.inlineDiffMargins = []; for (let i = 0, length = newDecorations.zones.length; i < length; i++) { const viewZone = newDecorations.zones[i]; - viewZone.suppressMouseDown = false; + viewZone.suppressMouseDown = true; let zoneId = viewChangeAccessor.addZone(viewZone); this._zones.push(zoneId); this._zonesMap[String(zoneId)] = true; if (newDecorations.zones[i].diff && viewZone.marginDomNode && this._clipboardService) { + viewZone.suppressMouseDown = false; this.inlineDiffMargins.push(new InlineDiffMargin(zoneId, viewZone.marginDomNode, editor, newDecorations.zones[i].diff!, this._contextMenuService, this._clipboardService)); } } From 40b5b5dde1dc38a2fadc372f405fe8f4fd122a87 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 6 Dec 2019 00:29:42 +0100 Subject: [PATCH 234/255] Fixes #84312: Add a time limit to bracket matching --- src/vs/editor/common/model.ts | 2 +- src/vs/editor/common/model/textModel.ts | 8 +++++++- src/vs/editor/contrib/bracketMatching/bracketMatching.ts | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index 0c6a851ec53..e26df375fa9 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -898,7 +898,7 @@ export interface ITextModel { * @param position The position at which to start the search. * @internal */ - findEnclosingBrackets(position: IPosition): [Range, Range] | null; + findEnclosingBrackets(position: IPosition, maxDuration?: number): [Range, Range] | null; /** * Given a `position`, if the position is on top or near a bracket, diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index ba78e7b90e0..9bc6dc21a62 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -34,6 +34,7 @@ import { withUndefinedAsNull } from 'vs/base/common/types'; import { VSBufferReadableStream, VSBuffer } from 'vs/base/common/buffer'; import { TokensStore, MultilineTokens, countEOL, MultilineTokens2, TokensStore2 } from 'vs/editor/common/model/tokensStore'; import { Color } from 'vs/base/common/color'; +import { Constants } from 'vs/base/common/uint'; function createTextBufferBuilder() { return new PieceTreeTextBufferBuilder(); @@ -2341,7 +2342,7 @@ export class TextModel extends Disposable implements model.ITextModel { return null; } - public findEnclosingBrackets(_position: IPosition): [Range, Range] | null { + public findEnclosingBrackets(_position: IPosition, maxDuration = Constants.MAX_SAFE_SMALL_INTEGER): [Range, Range] | null { const position = this.validatePosition(_position); const lineCount = this.getLineCount(); const savedCounts = new Map(); @@ -2385,7 +2386,12 @@ export class TextModel extends Disposable implements model.ITextModel { let languageId: LanguageId = -1; let modeBrackets: RichEditBrackets | null = null; + const startTime = Date.now(); for (let lineNumber = position.lineNumber; lineNumber <= lineCount; lineNumber++) { + const elapsedTime = Date.now() - startTime; + if (elapsedTime > maxDuration) { + return null; + } const lineTokens = this._getLineTokens(lineNumber); const tokenCount = lineTokens.getCount(); const lineText = this._buffer.getLineContent(lineNumber); diff --git a/src/vs/editor/contrib/bracketMatching/bracketMatching.ts b/src/vs/editor/contrib/bracketMatching/bracketMatching.ts index 2bb287315ee..a669b572023 100644 --- a/src/vs/editor/contrib/bracketMatching/bracketMatching.ts +++ b/src/vs/editor/contrib/bracketMatching/bracketMatching.ts @@ -334,7 +334,7 @@ export class BracketMatchingController extends Disposable implements editorCommo let brackets = model.matchBracket(position); let options = BracketMatchingController._DECORATION_OPTIONS_WITH_OVERVIEW_RULER; if (!brackets && this._matchBrackets === 'always') { - brackets = model.findEnclosingBrackets(position); + brackets = model.findEnclosingBrackets(position, 20 /* give at most 20ms to compute */); options = BracketMatchingController._DECORATION_OPTIONS_WITHOUT_OVERVIEW_RULER; } newData[newDataLen++] = new BracketsData(position, brackets, options); From 58bf19619b45c328e77b2eeb698abf9a110638ff Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 6 Dec 2019 00:28:30 +0100 Subject: [PATCH 235/255] update telemetry --- .../contrib/markers/browser/markersPanel.ts | 21 +++++++++++++++++++ .../markers/browser/markersPanelActions.ts | 20 ------------------ 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/vs/workbench/contrib/markers/browser/markersPanel.ts b/src/vs/workbench/contrib/markers/browser/markersPanel.ts index f2159a61fea..0041bade2ba 100644 --- a/src/vs/workbench/contrib/markers/browser/markersPanel.ts +++ b/src/vs/workbench/contrib/markers/browser/markersPanel.ts @@ -440,6 +440,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { })); this._register(this.tree.onDidChangeSelection(() => this.onSelected())); this._register(this.filterAction.onDidChange((event: IMarkersFilterActionChangeEvent) => { + this.reportFilteringUsed(); if (event.activeFile) { this.refreshPanel(); } else if (event.filterText || event.excludedFiles || event.showWarnings || event.showErrors || event.showInfos) { @@ -753,6 +754,26 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { return { source, code }; } + private reportFilteringUsed(): void { + const data = { + errors: this.filterAction.showErrors, + warnings: this.filterAction.showWarnings, + infos: this.filterAction.showInfos, + activeFile: this.filterAction.activeFile, + excludedFiles: this.filterAction.excludedFiles, + }; + /* __GDPR__ + "problems.filter" : { + "errors" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "warnings": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "infos": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "activeFile": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "excludedFiles": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + } + */ + this.telemetryService.publicLog('problems.filter', data); + } + protected saveState(): void { this.panelState['filter'] = this.filterAction.filterText; this.panelState['filterHistory'] = this.filterAction.filterHistory; diff --git a/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts b/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts index 47dca87ec67..39719499d5d 100644 --- a/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts +++ b/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts @@ -27,7 +27,6 @@ import { Marker } from 'vs/workbench/contrib/markers/browser/markersModel'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Event, Emitter } from 'vs/base/common/event'; import { FilterOptions } from 'vs/workbench/contrib/markers/browser/markersFilterOptions'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdown'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; @@ -285,7 +284,6 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { @IInstantiationService private readonly instantiationService: IInstantiationService, @IContextViewService private readonly contextViewService: IContextViewService, @IThemeService private readonly themeService: IThemeService, - @ITelemetryService private readonly telemetryService: ITelemetryService, @IContextKeyService contextKeyService: IContextKeyService ) { super(null, action); @@ -385,7 +383,6 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { inputbox.addToHistory(); this.action.filterText = inputbox.value; this.action.filterHistory = inputbox.getHistory(); - this.reportFilteringUsed(); } private updateBadge(): void { @@ -426,23 +423,6 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { } } - private reportFilteringUsed(): void { - const filterOptions = this.filterController.getFilterOptions(); - const data = { - errors: filterOptions.showErrors, - warnings: filterOptions.showWarnings, - infos: filterOptions.showInfos, - }; - /* __GDPR__ - "problems.filter" : { - "errors" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "warnings": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "infos": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - } - */ - this.telemetryService.publicLog('problems.filter', data); - } - protected updateClass(): void { if (this.element && this.container) { this.element.className = this.action.class || ''; From 196aeda027bd5fd10367d5bbd6d8faf8d45f0f40 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 6 Dec 2019 00:53:57 +0100 Subject: [PATCH 236/255] Fixes #86165: Do not always cancel the timeout, reduce the delay --- src/vs/editor/common/services/modelServiceImpl.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/common/services/modelServiceImpl.ts b/src/vs/editor/common/services/modelServiceImpl.ts index 6d309ab6aa5..4ba21e84bf6 100644 --- a/src/vs/editor/common/services/modelServiceImpl.ts +++ b/src/vs/editor/common/services/modelServiceImpl.ts @@ -652,11 +652,15 @@ class ModelSemanticColoring extends Disposable { this._isDisposed = false; this._model = model; this._semanticStyling = stylingProvider; - this._fetchSemanticTokens = this._register(new RunOnceScheduler(() => this._fetchSemanticTokensNow(), 500)); + this._fetchSemanticTokens = this._register(new RunOnceScheduler(() => this._fetchSemanticTokensNow(), 300)); this._currentResponse = null; this._currentRequestCancellationTokenSource = null; - this._register(this._model.onDidChangeContent(e => this._fetchSemanticTokens.schedule())); + this._register(this._model.onDidChangeContent(e => { + if (!this._fetchSemanticTokens.isScheduled()) { + this._fetchSemanticTokens.schedule(); + } + })); this._register(SemanticTokensProviderRegistry.onDidChange(e => this._fetchSemanticTokens.schedule())); if (themeService) { // workaround for tests which use undefined... :/ @@ -887,7 +891,9 @@ class ModelSemanticColoring extends Disposable { } } - this._fetchSemanticTokens.schedule(); + if (!this._fetchSemanticTokens.isScheduled()) { + this._fetchSemanticTokens.schedule(); + } } this._model.setSemanticTokens(result); From c197f24c69157b56dec008d307801f003dd74aec Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 6 Dec 2019 01:11:56 +0100 Subject: [PATCH 237/255] Fixes #84523: Pass in comparator when using Array.sort() --- .../viewParts/currentLineHighlight/currentLineHighlight.ts | 2 +- src/vs/editor/common/config/editorOptions.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts b/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts index cb3e355d8c6..59fdaf045a9 100644 --- a/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts +++ b/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts @@ -57,7 +57,7 @@ export abstract class AbstractLineHighlightOverlay extends DynamicViewOverlay { const renderSelections = isRenderedUsingBorder ? this._selections.slice(0, 1) : this._selections; const cursorsLineNumbers = renderSelections.map(s => s.positionLineNumber); - cursorsLineNumbers.sort(); + cursorsLineNumbers.sort((a, b) => a - b); if (!arrays.equals(this._cursorLineNumbers, cursorsLineNumbers)) { this._cursorLineNumbers = cursorsLineNumbers; hasChanged = true; diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 74662b49749..a5c35ef4b60 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -2271,7 +2271,7 @@ class EditorRulers extends SimpleEditorOption { for (let value of input) { rulers.push(EditorIntOption.clampedInt(value, 0, 0, 10000)); } - rulers.sort(); + rulers.sort((a, b) => a - b); return rulers; } return this.defaultValue; From ca424255035441c4ea439b82df3eec334572f267 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Thu, 5 Dec 2019 16:16:52 -0800 Subject: [PATCH 238/255] Fix #83599. Refresh code widget focus state when hide --- src/vs/base/browser/dom.ts | 18 ++++++++++++++++++ .../editor/browser/widget/codeEditorWidget.ts | 7 +++++++ 2 files changed, 25 insertions(+) diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 7a582b4e018..a377409075c 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -972,6 +972,7 @@ export const EventHelper = { export interface IFocusTracker extends Disposable { onDidFocus: Event; onDidBlur: Event; + refreshState?(): void; } export function saveParentsScrollTop(node: Element): number[] { @@ -1000,6 +1001,8 @@ class FocusTracker extends Disposable implements IFocusTracker { private readonly _onDidBlur = this._register(new Emitter()); public readonly onDidBlur: Event = this._onDidBlur.event; + private _refreshStateHandler: () => void; + constructor(element: HTMLElement | Window) { super(); let hasFocus = isAncestor(document.activeElement, element); @@ -1026,9 +1029,24 @@ class FocusTracker extends Disposable implements IFocusTracker { } }; + this._refreshStateHandler = () => { + let currentNodeHasFocus = isAncestor(document.activeElement, element); + if (currentNodeHasFocus !== hasFocus) { + if (hasFocus) { + onBlur(); + } else { + onFocus(); + } + } + }; + this._register(domEvent(element, EventType.FOCUS, true)(onFocus)); this._register(domEvent(element, EventType.BLUR, true)(onBlur)); } + + refreshState() { + this._refreshStateHandler(); + } } export function trackFocus(element: HTMLElement | Window): IFocusTracker { diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 357eeab2a9f..4bfde519d76 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -874,6 +874,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE public onHide(): void { this._modelData?.view.refreshFocusState(); + this._focusTracker.refreshState(); } public getContribution(id: string): T { @@ -1806,6 +1807,12 @@ class CodeEditorWidgetFocusTracker extends Disposable { public hasFocus(): boolean { return this._hasFocus; } + + public refreshState(): void { + if (this._domFocusTracker.refreshState) { + this._domFocusTracker.refreshState(); + } + } } const squigglyStart = encodeURIComponent(` + * | + */ + if (charBeforePrimarySelection === '\n' && charBeforeSecondarySelection === '\n') { + return false; + } + /** + * Special case for exiting + *
| + *
| + */ + if (charAfterPrimarySelection === '\n' && charAfterSecondarySelection === '\n') { + return false; + } + // Exit mirror mode when cursor position no longer mirror // Unless it's in the case of `<|>` const charBeforeBothPositionRoughlyEqual = From 06f97fe829c3bcfa148082702e313e939e4287e1 Mon Sep 17 00:00:00 2001 From: Pine Wu Date: Thu, 5 Dec 2019 16:24:58 -0800 Subject: [PATCH 240/255] Fix #86441 --- .../client/src/mirrorCursor.ts | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/extensions/html-language-features/client/src/mirrorCursor.ts b/extensions/html-language-features/client/src/mirrorCursor.ts index 2fc409e8ab0..43ea3169c97 100644 --- a/extensions/html-language-features/client/src/mirrorCursor.ts +++ b/extensions/html-language-features/client/src/mirrorCursor.ts @@ -87,9 +87,22 @@ export function activateMirrorCursor( } } + const exitMirrorMode = () => { + inMirrorMode = false; + window.activeTextEditor!.selections = [window.activeTextEditor!.selections[0]]; + }; + if (cursors.length === 2 && inMirrorMode) { - // Check two cases if (event.selections[0].isEmpty && event.selections[1].isEmpty) { + if ( + prevCursors.length === 2 && + event.selections[0].anchor.line !== prevCursors[0].anchor.line && + event.selections[1].anchor.line !== prevCursors[0].anchor.line + ) { + exitMirrorMode(); + return; + } + const charBeforeAndAfterPositionsRoughtlyEqual = isCharBeforeAndAfterPositionsRoughtlyEqual( event.textEditor.document, event.selections[0].anchor, @@ -97,8 +110,7 @@ export function activateMirrorCursor( ); if (!charBeforeAndAfterPositionsRoughtlyEqual) { - inMirrorMode = false; - window.activeTextEditor!.selections = [window.activeTextEditor!.selections[0]]; + exitMirrorMode(); return; } else { // Need to cleanup in the case of
@@ -109,11 +121,10 @@ export function activateMirrorCursor( event.selections[1].anchor ) ) { - inMirrorMode = false; const cleanupEdit = new WorkspaceEdit(); const cleanupRange = new Range(event.selections[1].anchor.translate(0, -1), event.selections[1].anchor); cleanupEdit.replace(event.textEditor.document.uri, cleanupRange, ''); - window.activeTextEditor!.selections = [window.activeTextEditor!.selections[0]]; + exitMirrorMode(); workspace.applyEdit(cleanupEdit); } } From 29474d526eb5d9855d68db00cb129b4682416730 Mon Sep 17 00:00:00 2001 From: Pine Wu Date: Thu, 5 Dec 2019 16:26:18 -0800 Subject: [PATCH 241/255] Revert "Fix #85151" This reverts commit 398696040b3a5eee0319cc740b38eb3edb311f41. --- .../client/src/mirrorCursor.ts | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/extensions/html-language-features/client/src/mirrorCursor.ts b/extensions/html-language-features/client/src/mirrorCursor.ts index 43ea3169c97..8f6f3ca6db5 100644 --- a/extensions/html-language-features/client/src/mirrorCursor.ts +++ b/extensions/html-language-features/client/src/mirrorCursor.ts @@ -161,36 +161,6 @@ function isCharBeforeAndAfterPositionsRoughtlyEqual(document: TextDocument, firs const charBeforeSecondarySelection = getCharBefore(document, secondPos); const charAfterSecondarySelection = getCharAfter(document, secondPos); - /** - * Special case for exiting - * |
- * |
- */ - if ( - charBeforePrimarySelection === ' ' && - charBeforeSecondarySelection === ' ' && - charAfterPrimarySelection === '<' && - charAfterSecondarySelection === '<' - ) { - return false; - } - /** - * Special case for exiting - * |
- * |
- */ - if (charBeforePrimarySelection === '\n' && charBeforeSecondarySelection === '\n') { - return false; - } - /** - * Special case for exiting - *
| - *
| - */ - if (charAfterPrimarySelection === '\n' && charAfterSecondarySelection === '\n') { - return false; - } - // Exit mirror mode when cursor position no longer mirror // Unless it's in the case of `<|>` const charBeforeBothPositionRoughlyEqual = From 7fada393922b5e025afce58d441b2d904808630e Mon Sep 17 00:00:00 2001 From: Pine Wu Date: Thu, 5 Dec 2019 16:27:35 -0800 Subject: [PATCH 242/255] Fix #86151 --- .../client/src/mirrorCursor.ts | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/extensions/html-language-features/client/src/mirrorCursor.ts b/extensions/html-language-features/client/src/mirrorCursor.ts index 8f6f3ca6db5..43ea3169c97 100644 --- a/extensions/html-language-features/client/src/mirrorCursor.ts +++ b/extensions/html-language-features/client/src/mirrorCursor.ts @@ -161,6 +161,36 @@ function isCharBeforeAndAfterPositionsRoughtlyEqual(document: TextDocument, firs const charBeforeSecondarySelection = getCharBefore(document, secondPos); const charAfterSecondarySelection = getCharAfter(document, secondPos); + /** + * Special case for exiting + * |
+ * |
+ */ + if ( + charBeforePrimarySelection === ' ' && + charBeforeSecondarySelection === ' ' && + charAfterPrimarySelection === '<' && + charAfterSecondarySelection === '<' + ) { + return false; + } + /** + * Special case for exiting + * |
+ * |
+ */ + if (charBeforePrimarySelection === '\n' && charBeforeSecondarySelection === '\n') { + return false; + } + /** + * Special case for exiting + *
| + *
| + */ + if (charAfterPrimarySelection === '\n' && charAfterSecondarySelection === '\n') { + return false; + } + // Exit mirror mode when cursor position no longer mirror // Unless it's in the case of `<|>` const charBeforeBothPositionRoughlyEqual = From 33941fef36e945d368135482a8d12e2cfb16b2a9 Mon Sep 17 00:00:00 2001 From: Pine Wu Date: Thu, 5 Dec 2019 16:37:23 -0800 Subject: [PATCH 243/255] Fix #85841 --- .../services/preferences/common/preferencesModels.ts | 4 ++-- .../preferences/test/common/preferencesModel.test.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/services/preferences/common/preferencesModels.ts b/src/vs/workbench/services/preferences/common/preferencesModels.ts index 1406adf1750..91c30d0ef26 100644 --- a/src/vs/workbench/services/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/services/preferences/common/preferencesModels.ts @@ -1051,12 +1051,12 @@ export function createValidator(prop: IConfigurationPropertySchema): (value: any } if (prop.minItems && stringArrayValue.length < prop.minItems) { - message += nls.localize('validations.stringArrayMinItem', 'Array must have at least {0} items', prop.minItems); + message += nls.localize('validations.stringArrayMinItem', 'Array should have at least {0} items', prop.minItems); message += '\n'; } if (prop.maxItems && stringArrayValue.length > prop.maxItems) { - message += nls.localize('validations.stringArrayMaxItem', 'Array must have less than {0} items', prop.maxItems); + message += nls.localize('validations.stringArrayMaxItem', 'Array should have at most {0} items', prop.maxItems); message += '\n'; } diff --git a/src/vs/workbench/services/preferences/test/common/preferencesModel.test.ts b/src/vs/workbench/services/preferences/test/common/preferencesModel.test.ts index 9fbfb467a2b..fc0f4db07c4 100644 --- a/src/vs/workbench/services/preferences/test/common/preferencesModel.test.ts +++ b/src/vs/workbench/services/preferences/test/common/preferencesModel.test.ts @@ -288,10 +288,10 @@ suite('Preferences Model test', () => { test('min-max items array', () => { { const arr = new ArrayTester({ type: 'array', items: { type: 'string' }, minItems: 1, maxItems: 2 }); - arr.rejects([]).withMessage('Array must have at least 1 items'); + arr.rejects([]).withMessage('Array should have at least 1 items'); arr.accepts(['a']); arr.accepts(['a', 'a']); - arr.rejects(['a', 'a', 'a']).withMessage('Array must have less than 2 items'); + arr.rejects(['a', 'a', 'a']).withMessage('Array should have at most 2 items'); } }); @@ -312,7 +312,7 @@ suite('Preferences Model test', () => { test('min-max and enum', () => { const arr = new ArrayTester({ type: 'array', items: { type: 'string', enum: ['a', 'b'] }, minItems: 1, maxItems: 2 }); - arr.rejects(['a', 'b', 'c']).withMessage('Array must have less than 2 items'); + arr.rejects(['a', 'b', 'c']).withMessage('Array should have at most 2 items'); arr.rejects(['a', 'b', 'c']).withMessage(`Value 'c' is not one of`); }); From 35a8880109f28bbfc1dcc9f5305d32c195b60897 Mon Sep 17 00:00:00 2001 From: Pine Wu Date: Thu, 5 Dec 2019 16:57:11 -0800 Subject: [PATCH 244/255] should -> must for #85841 --- .../services/preferences/common/preferencesModels.ts | 4 ++-- .../preferences/test/common/preferencesModel.test.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/services/preferences/common/preferencesModels.ts b/src/vs/workbench/services/preferences/common/preferencesModels.ts index 91c30d0ef26..4fc5403acf3 100644 --- a/src/vs/workbench/services/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/services/preferences/common/preferencesModels.ts @@ -1051,12 +1051,12 @@ export function createValidator(prop: IConfigurationPropertySchema): (value: any } if (prop.minItems && stringArrayValue.length < prop.minItems) { - message += nls.localize('validations.stringArrayMinItem', 'Array should have at least {0} items', prop.minItems); + message += nls.localize('validations.stringArrayMinItem', 'Array must have at least {0} items', prop.minItems); message += '\n'; } if (prop.maxItems && stringArrayValue.length > prop.maxItems) { - message += nls.localize('validations.stringArrayMaxItem', 'Array should have at most {0} items', prop.maxItems); + message += nls.localize('validations.stringArrayMaxItem', 'Array must have at most {0} items', prop.maxItems); message += '\n'; } diff --git a/src/vs/workbench/services/preferences/test/common/preferencesModel.test.ts b/src/vs/workbench/services/preferences/test/common/preferencesModel.test.ts index fc0f4db07c4..65673979df4 100644 --- a/src/vs/workbench/services/preferences/test/common/preferencesModel.test.ts +++ b/src/vs/workbench/services/preferences/test/common/preferencesModel.test.ts @@ -288,10 +288,10 @@ suite('Preferences Model test', () => { test('min-max items array', () => { { const arr = new ArrayTester({ type: 'array', items: { type: 'string' }, minItems: 1, maxItems: 2 }); - arr.rejects([]).withMessage('Array should have at least 1 items'); + arr.rejects([]).withMessage('Array must have at least 1 items'); arr.accepts(['a']); arr.accepts(['a', 'a']); - arr.rejects(['a', 'a', 'a']).withMessage('Array should have at most 2 items'); + arr.rejects(['a', 'a', 'a']).withMessage('Array must have at most 2 items'); } }); @@ -312,7 +312,7 @@ suite('Preferences Model test', () => { test('min-max and enum', () => { const arr = new ArrayTester({ type: 'array', items: { type: 'string', enum: ['a', 'b'] }, minItems: 1, maxItems: 2 }); - arr.rejects(['a', 'b', 'c']).withMessage('Array should have at most 2 items'); + arr.rejects(['a', 'b', 'c']).withMessage('Array must have at most 2 items'); arr.rejects(['a', 'b', 'c']).withMessage(`Value 'c' is not one of`); }); From bf78b9b6c02cce9121026a9fe0641057d71a86c4 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 6 Dec 2019 09:10:44 +0100 Subject: [PATCH 245/255] use findMatchHighlight as default color for symbolHighlight, #84553 --- src/vs/editor/common/view/editorColorRegistry.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/common/view/editorColorRegistry.ts b/src/vs/editor/common/view/editorColorRegistry.ts index 2901c23cc75..fbdb785c49b 100644 --- a/src/vs/editor/common/view/editorColorRegistry.ts +++ b/src/vs/editor/common/view/editorColorRegistry.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import { Color, RGBA } from 'vs/base/common/color'; -import { activeContrastBorder, editorBackground, editorForeground, registerColor, editorWarningForeground, editorInfoForeground, editorWarningBorder, editorInfoBorder, contrastBorder } from 'vs/platform/theme/common/colorRegistry'; +import { activeContrastBorder, editorBackground, editorForeground, registerColor, editorWarningForeground, editorInfoForeground, editorWarningBorder, editorInfoBorder, contrastBorder, editorFindMatchHighlight } from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; /** @@ -15,7 +15,7 @@ export const editorLineHighlight = registerColor('editor.lineHighlightBackground export const editorLineHighlightBorder = registerColor('editor.lineHighlightBorder', { dark: '#282828', light: '#eeeeee', hc: '#f38518' }, nls.localize('lineHighlightBorderBox', 'Background color for the border around the line at the cursor position.')); export const editorRangeHighlight = registerColor('editor.rangeHighlightBackground', { dark: '#ffffff0b', light: '#fdff0033', hc: null }, nls.localize('rangeHighlight', 'Background color of highlighted ranges, like by quick open and find features. The color must not be opaque so as not to hide underlying decorations.'), true); export const editorRangeHighlightBorder = registerColor('editor.rangeHighlightBorder', { dark: null, light: null, hc: activeContrastBorder }, nls.localize('rangeHighlightBorder', 'Background color of the border around highlighted ranges.'), true); -export const editorSymbolHighlight = registerColor('editor.symbolHighlightBackground', { dark: editorRangeHighlight, light: editorRangeHighlight, hc: null }, nls.localize('symbolHighlight', 'Background color of highlighted symbol, like for go to definition or go next/previous symbol. The color must not be opaque so as not to hide underlying decorations.'), true); +export const editorSymbolHighlight = registerColor('editor.symbolHighlightBackground', { dark: editorFindMatchHighlight, light: editorFindMatchHighlight, hc: null }, nls.localize('symbolHighlight', 'Background color of highlighted symbol, like for go to definition or go next/previous symbol. The color must not be opaque so as not to hide underlying decorations.'), true); export const editorSymbolHighlightBorder = registerColor('editor.symbolHighlightBorder', { dark: null, light: null, hc: activeContrastBorder }, nls.localize('symbolHighlightBorder', 'Background color of the border around highlighted symbols.'), true); export const editorCursorForeground = registerColor('editorCursor.foreground', { dark: '#AEAFAD', light: Color.black, hc: Color.white }, nls.localize('caret', 'Color of the editor cursor.')); From a3d4c6d37f6099d0d35ca4c8e9389ce1a335ad19 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Fri, 6 Dec 2019 10:35:35 +0100 Subject: [PATCH 246/255] Ensure that the remote explorer isn't losing views Fixes https://github.com/microsoft/vscode-remote-release/issues/1902 --- src/vs/workbench/browser/parts/views/viewsViewlet.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/workbench/browser/parts/views/viewsViewlet.ts b/src/vs/workbench/browser/parts/views/viewsViewlet.ts index b178ef2f317..13968eb74c9 100644 --- a/src/vs/workbench/browser/parts/views/viewsViewlet.ts +++ b/src/vs/workbench/browser/parts/views/viewsViewlet.ts @@ -379,6 +379,9 @@ export abstract class FilterViewContainerViewlet extends ViewContainerViewlet { protected abstract getFilterOn(viewDescriptor: IViewDescriptor): string | undefined; private onFilterChanged(newFilterValue: string) { + if (this.allViews.size === 0) { + this.updateAllViews(this.viewsModel.viewDescriptors); + } this.getViewsNotForTarget(newFilterValue).forEach(item => this.viewsModel.setVisible(item.id, false)); this.getViewsForTarget(newFilterValue).forEach(item => this.viewsModel.setVisible(item.id, true)); } From 08b0a9bc59793f85e0ad5d84242c902f26706d2d Mon Sep 17 00:00:00 2001 From: isidor Date: Fri, 6 Dec 2019 10:40:38 +0100 Subject: [PATCH 247/255] Persisted data breakpoints are not registered when starting a new debug session fixes #83743 --- .../api/browser/mainThreadDebugService.ts | 4 ++-- src/vs/workbench/api/common/extHost.protocol.ts | 1 + .../contrib/debug/browser/breakpointsView.ts | 2 +- .../contrib/debug/browser/debugService.ts | 6 +++--- .../contrib/debug/browser/variablesView.ts | 2 +- src/vs/workbench/contrib/debug/common/debug.ts | 6 +++--- .../workbench/contrib/debug/common/debugModel.ts | 14 ++++++++------ 7 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadDebugService.ts b/src/vs/workbench/api/browser/mainThreadDebugService.ts index 41005c20e4e..673177f0f01 100644 --- a/src/vs/workbench/api/browser/mainThreadDebugService.ts +++ b/src/vs/workbench/api/browser/mainThreadDebugService.ts @@ -141,7 +141,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb } else if (dto.type === 'function') { this.debugService.addFunctionBreakpoint(dto.functionName, dto.id); } else if (dto.type === 'data') { - this.debugService.addDataBreakpoint(dto.label, dto.dataId, dto.canPersist); + this.debugService.addDataBreakpoint(dto.label, dto.dataId, dto.canPersist, dto.accessTypes); } } return Promise.resolve(); @@ -336,7 +336,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb condition: dbp.condition, hitCondition: dbp.hitCondition, logMessage: dbp.logMessage, - label: dbp.label, + label: dbp.description, canPersist: dbp.canPersist }; } else { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index b7d4837a61e..3dab81c9c55 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1287,6 +1287,7 @@ export interface IDataBreakpointDto extends IBreakpointDto { dataId: string; canPersist: boolean; label: string; + accessTypes?: DebugProtocol.DataBreakpointAccessType[]; } export interface ISourceBreakpointDto extends IBreakpointDto { diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index d9711706037..8d19e351841 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -500,7 +500,7 @@ class DataBreakpointsRenderer implements IListRenderer { - this.model.addDataBreakpoint(label, dataId, canPersist); + async addDataBreakpoint(label: string, dataId: string, canPersist: boolean, accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined): Promise { + this.model.addDataBreakpoint(label, dataId, canPersist, accessTypes); await this.sendDataBreakpoints(); this.storeBreakpoints(); @@ -1100,7 +1100,7 @@ export class DebugService implements IDebugService { let result: DataBreakpoint[] | undefined; try { result = JSON.parse(this.storageService.get(DEBUG_DATA_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((dbp: any) => { - return new DataBreakpoint(dbp.label, dbp.dataId, true, dbp.enabled, dbp.hitCondition, dbp.condition, dbp.logMessage); + return new DataBreakpoint(dbp.description, dbp.dataId, true, dbp.enabled, dbp.hitCondition, dbp.condition, dbp.logMessage, dbp.accessTypes); }); } catch (e) { } diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index 81012b401ae..fb82b157b50 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -185,7 +185,7 @@ export class VariablesView extends ViewletPane { if (dataid) { actions.push(new Separator()); actions.push(new Action('debug.breakWhenValueChanges', nls.localize('breakWhenValueChanges', "Break When Value Changes"), undefined, true, () => { - return this.debugService.addDataBreakpoint(response.description, dataid, !!response.canPersist); + return this.debugService.addDataBreakpoint(response.description, dataid, !!response.canPersist, response.accessTypes); })); } } diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 9b87af8cbe4..88ac5aa6786 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -215,7 +215,7 @@ export interface IDebugSession extends ITreeElement { sendBreakpoints(modelUri: uri, bpts: IBreakpoint[], sourceModified: boolean): Promise; sendFunctionBreakpoints(fbps: IFunctionBreakpoint[]): Promise; - dataBreakpointInfo(name: string, variablesReference?: number): Promise<{ dataId: string | null, description: string, canPersist?: boolean }>; + dataBreakpointInfo(name: string, variablesReference?: number): Promise<{ dataId: string | null, description: string, canPersist?: boolean, accessTypes?: DebugProtocol.DataBreakpointAccessType[] }>; sendDataBreakpoints(dbps: IDataBreakpoint[]): Promise; sendExceptionBreakpoints(exbpts: IExceptionBreakpoint[]): Promise; breakpointsLocations(uri: uri, lineNumber: number): Promise; @@ -377,7 +377,7 @@ export interface IExceptionBreakpoint extends IEnablement { } export interface IDataBreakpoint extends IBaseBreakpoint { - readonly label: string; + readonly description: string; readonly dataId: string; readonly canPersist: boolean; } @@ -795,7 +795,7 @@ export interface IDebugService { /** * Adds a new data breakpoint. */ - addDataBreakpoint(label: string, dataId: string, canPersist: boolean): Promise; + addDataBreakpoint(label: string, dataId: string, canPersist: boolean, accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined): Promise; /** * Removes all data breakpoints. If id is passed only removes the data breakpoint with the passed id. diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index 311ac19dcf7..5ad9aa7d55d 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -748,13 +748,14 @@ export class FunctionBreakpoint extends BaseBreakpoint implements IFunctionBreak export class DataBreakpoint extends BaseBreakpoint implements IDataBreakpoint { constructor( - public label: string, + public description: string, public dataId: string, public canPersist: boolean, enabled: boolean, hitCondition: string | undefined, condition: string | undefined, logMessage: string | undefined, + private accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined, id = generateUuid() ) { super(enabled, hitCondition, condition, logMessage, id); @@ -762,8 +763,9 @@ export class DataBreakpoint extends BaseBreakpoint implements IDataBreakpoint { toJSON(): any { const result = super.toJSON(); - result.label = this.label; - result.dataid = this.dataId; + result.description = this.description; + result.dataId = this.dataId; + result.accessTypes = this.accessTypes; return result; } @@ -777,7 +779,7 @@ export class DataBreakpoint extends BaseBreakpoint implements IDataBreakpoint { } toString(): string { - return this.label; + return this.description; } } @@ -1159,8 +1161,8 @@ export class DebugModel implements IDebugModel { this._onDidChangeBreakpoints.fire({ removed }); } - addDataBreakpoint(label: string, dataId: string, canPersist: boolean): void { - const newDataBreakpoint = new DataBreakpoint(label, dataId, canPersist, true, undefined, undefined, undefined); + addDataBreakpoint(label: string, dataId: string, canPersist: boolean, accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined): void { + const newDataBreakpoint = new DataBreakpoint(label, dataId, canPersist, true, undefined, undefined, undefined, accessTypes); this.dataBreakopints.push(newDataBreakpoint); this._onDidChangeBreakpoints.fire({ added: [newDataBreakpoint] }); } From 54151bb664bd866c960866cf3c4926ad42a862dd Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 6 Dec 2019 12:23:17 +0100 Subject: [PATCH 248/255] [semantic tokens] Use singular for token type names Fixes #86281 --- .../src/colorizerTestMain.ts | 2 +- .../common/tokenClassificationRegistry.ts | 61 +++++----- .../tokenStyleResolving.test.ts | 106 +++++++++--------- 3 files changed, 87 insertions(+), 82 deletions(-) diff --git a/extensions/vscode-colorize-tests/src/colorizerTestMain.ts b/extensions/vscode-colorize-tests/src/colorizerTestMain.ts index e9536994cf7..7f30a330ffb 100644 --- a/extensions/vscode-colorize-tests/src/colorizerTestMain.ts +++ b/extensions/vscode-colorize-tests/src/colorizerTestMain.ts @@ -8,7 +8,7 @@ import * as jsoncParser from 'jsonc-parser'; export function activate(context: vscode.ExtensionContext): any { - const tokenTypes = ['types', 'structs', 'classes', 'interfaces', 'enums', 'parameterTypes', 'functions', 'variables']; + const tokenTypes = ['type', 'struct', 'class', 'interface', 'enum', 'parameterType', 'function', 'variable']; const tokenModifiers = ['static', 'abstract', 'deprecated', 'declaration', 'documentation', 'member', 'async']; const legend = new vscode.SemanticTokensLegend(tokenTypes, tokenModifiers); diff --git a/src/vs/platform/theme/common/tokenClassificationRegistry.ts b/src/vs/platform/theme/common/tokenClassificationRegistry.ts index 3fa3e0c9c46..fa4303f57a9 100644 --- a/src/vs/platform/theme/common/tokenClassificationRegistry.ts +++ b/src/vs/platform/theme/common/tokenClassificationRegistry.ts @@ -320,40 +320,45 @@ export function getTokenClassificationRegistry(): ITokenClassificationRegistry { return tokenClassificationRegistry; } -export const comments = registerTokenType('comments', nls.localize('comments', "Style for comments."), [['comment']]); -export const strings = registerTokenType('strings', nls.localize('strings', "Style for strings."), [['string']]); -export const keywords = registerTokenType('keywords', nls.localize('keywords', "Style for keywords."), [['keyword.control']]); -export const numbers = registerTokenType('numbers', nls.localize('numbers', "Style for numbers."), [['constant.numeric']]); -export const regexp = registerTokenType('regexp', nls.localize('regexp', "Style for expressions."), [['constant.regexp']]); -export const operators = registerTokenType('operators', nls.localize('operator', "Style for operators."), [['keyword.operator']]); +// default token types -export const namespaces = registerTokenType('namespaces', nls.localize('namespace', "Style for namespaces."), [['entity.name.namespace']]); +registerTokenType('comment', nls.localize('comment', "Style for comments."), [['comment']]); +registerTokenType('string', nls.localize('string', "Style for strings."), [['string']]); +registerTokenType('keyword', nls.localize('keyword', "Style for keywords."), [['keyword.control']]); +registerTokenType('number', nls.localize('number', "Style for numbers."), [['constant.numeric']]); +registerTokenType('regexp', nls.localize('regexp', "Style for expressions."), [['constant.regexp']]); +registerTokenType('operator', nls.localize('operator', "Style for operators."), [['keyword.operator']]); -export const types = registerTokenType('types', nls.localize('types', "Style for types."), [['entity.name.type'], ['entity.name.class'], ['support.type'], ['support.class']]); -export const structs = registerTokenType('structs', nls.localize('struct', "Style for structs."), [['storage.type.struct']], types); -export const classes = registerTokenType('classes', nls.localize('class', "Style for classes."), [['entity.name.class']], types); -export const interfaces = registerTokenType('interfaces', nls.localize('interface', "Style for interfaces."), undefined, types); -export const enums = registerTokenType('enums', nls.localize('enum', "Style for enums."), undefined, types); -export const parameterTypes = registerTokenType('parameterTypes', nls.localize('parameterType', "Style for parameter types."), undefined, types); +registerTokenType('namespace', nls.localize('namespace', "Style for namespaces."), [['entity.name.namespace']]); -export const functions = registerTokenType('functions', nls.localize('functions', "Style for functions"), [['entity.name.function'], ['support.function']]); -export const macros = registerTokenType('macros', nls.localize('macro', "Style for macros."), undefined, functions); +registerTokenType('type', nls.localize('type', "Style for types."), [['entity.name.type'], ['entity.name.class'], ['support.type'], ['support.class']]); +registerTokenType('struct', nls.localize('struct', "Style for structs."), [['storage.type.struct']], 'type'); +registerTokenType('class', nls.localize('class', "Style for classes."), [['entity.name.class']], 'type'); +registerTokenType('interface', nls.localize('interface', "Style for interfaces."), undefined, 'type'); +registerTokenType('enum', nls.localize('enum', "Style for enums."), undefined, 'type'); +registerTokenType('parameterType', nls.localize('parameterType', "Style for parameter types."), undefined, 'type'); -export const variables = registerTokenType('variables', nls.localize('variables', "Style for variables."), [['variable'], ['entity.name.variable']]); -export const constants = registerTokenType('constants', nls.localize('constants', "Style for constants."), undefined, variables); -export const parameters = registerTokenType('parameters', nls.localize('parameters', "Style for parameters."), undefined, variables); -export const property = registerTokenType('properties', nls.localize('properties', "Style for properties."), undefined, variables); +registerTokenType('function', nls.localize('function', "Style for functions"), [['entity.name.function'], ['support.function']]); +registerTokenType('macro', nls.localize('macro', "Style for macros."), undefined, 'function'); -export const labels = registerTokenType('labels', nls.localize('labels', "Style for labels. "), undefined); +registerTokenType('variable', nls.localize('variable', "Style for variables."), [['variable'], ['entity.name.variable']]); +registerTokenType('constant', nls.localize('constant', "Style for constants."), undefined, 'variable'); +registerTokenType('parameter', nls.localize('parameter', "Style for parameters."), undefined, 'variable'); +registerTokenType('property', nls.localize('propertie', "Style for properties."), undefined, 'variable'); + +registerTokenType('label', nls.localize('labels', "Style for labels. "), undefined); + +// default token modifiers + +registerTokenModifier('declaration', nls.localize('declaration', "Style for all symbol declarations."), undefined); +registerTokenModifier('documentation', nls.localize('documentation', "Style to use for references in documentation."), undefined); +registerTokenModifier('member', nls.localize('member', "Style to use for member functions, variables (fields) and types."), undefined); +registerTokenModifier('static', nls.localize('static', "Style to use for symbols that are static."), undefined); +registerTokenModifier('abstract', nls.localize('abstract', "Style to use for symbols that are abstract."), undefined); +registerTokenModifier('deprecated', nls.localize('deprecated', "Style to use for symbols that are deprecated."), undefined); +registerTokenModifier('modification', nls.localize('modification', "Style to use for write accesses."), undefined); +registerTokenModifier('async', nls.localize('async', "Style to use for symbols that are async."), undefined); -export const m_declaration = registerTokenModifier('declaration', nls.localize('declaration', "Style for all symbol declarations."), undefined); -export const m_documentation = registerTokenModifier('documentation', nls.localize('documentation', "Style to use for references in documentation."), undefined); -export const m_member = registerTokenModifier('member', nls.localize('member', "Style to use for member functions, variables (fields) and types."), undefined); -export const m_static = registerTokenModifier('static', nls.localize('static', "Style to use for symbols that are static."), undefined); -export const m_abstract = registerTokenModifier('abstract', nls.localize('abstract', "Style to use for symbols that are abstract."), undefined); -export const m_deprecated = registerTokenModifier('deprecated', nls.localize('deprecated', "Style to use for symbols that are deprecated."), undefined); -export const m_modification = registerTokenModifier('modification', nls.localize('modification', "Style to use for write accesses."), undefined); -export const m_async = registerTokenModifier('async', nls.localize('async', "Style to use for symbols that are async."), undefined); function bitCount(u: number) { // https://blogs.msdn.microsoft.com/jeuge/2005/06/08/bit-fiddling-3/ diff --git a/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts b/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts index b256164b7b4..0e84f4e22c4 100644 --- a/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts +++ b/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts @@ -6,7 +6,7 @@ import { ColorThemeData } from 'vs/workbench/services/themes/common/colorThemeData'; import * as assert from 'assert'; import { ITokenColorCustomizations } from 'vs/workbench/services/themes/common/workbenchThemeService'; -import { TokenStyle, comments, variables, types, functions, keywords, numbers, strings, getTokenClassificationRegistry } from 'vs/platform/theme/common/tokenClassificationRegistry'; +import { TokenStyle, getTokenClassificationRegistry } from 'vs/platform/theme/common/tokenClassificationRegistry'; import { Color } from 'vs/base/common/color'; import { isString } from 'vs/base/common/types'; import { FileService } from 'vs/platform/files/common/fileService'; @@ -107,13 +107,13 @@ suite('Themes - TokenStyleResolving', () => { assert.equal(themeData.isLoaded, true); assertTokenStyles(themeData, { - [comments]: ts('#88846f', undefinedStyle), - [variables]: ts('#F8F8F2', unsetStyle), - [types]: ts('#A6E22E', { underline: true }), - [functions]: ts('#A6E22E', unsetStyle), - [strings]: ts('#E6DB74', undefinedStyle), - [numbers]: ts('#AE81FF', undefinedStyle), - [keywords]: ts('#F92672', undefinedStyle) + 'comment': ts('#88846f', undefinedStyle), + 'variable': ts('#F8F8F2', unsetStyle), + 'type': ts('#A6E22E', { underline: true }), + 'function': ts('#A6E22E', unsetStyle), + 'string': ts('#E6DB74', undefinedStyle), + 'number': ts('#AE81FF', undefinedStyle), + 'keyword': ts('#F92672', undefinedStyle) }); }); @@ -127,13 +127,13 @@ suite('Themes - TokenStyleResolving', () => { assert.equal(themeData.isLoaded, true); assertTokenStyles(themeData, { - [comments]: ts('#6A9955', undefinedStyle), - [variables]: ts('#9CDCFE', undefinedStyle), - [types]: ts('#4EC9B0', undefinedStyle), - [functions]: ts('#DCDCAA', undefinedStyle), - [strings]: ts('#CE9178', undefinedStyle), - [numbers]: ts('#B5CEA8', undefinedStyle), - [keywords]: ts('#C586C0', undefinedStyle) + 'comment': ts('#6A9955', undefinedStyle), + 'variable': ts('#9CDCFE', undefinedStyle), + 'type': ts('#4EC9B0', undefinedStyle), + 'function': ts('#DCDCAA', undefinedStyle), + 'string': ts('#CE9178', undefinedStyle), + 'number': ts('#B5CEA8', undefinedStyle), + 'keyword': ts('#C586C0', undefinedStyle) }); }); @@ -147,13 +147,13 @@ suite('Themes - TokenStyleResolving', () => { assert.equal(themeData.isLoaded, true); assertTokenStyles(themeData, { - [comments]: ts('#008000', undefinedStyle), - [variables]: ts(undefined, undefinedStyle), - [types]: ts(undefined, undefinedStyle), - [functions]: ts(undefined, undefinedStyle), - [strings]: ts('#a31515', undefinedStyle), - [numbers]: ts('#09885a', undefinedStyle), - [keywords]: ts('#0000ff', undefinedStyle) + 'comment': ts('#008000', undefinedStyle), + 'variable': ts(undefined, undefinedStyle), + 'type': ts(undefined, undefinedStyle), + 'function': ts(undefined, undefinedStyle), + 'string': ts('#a31515', undefinedStyle), + 'number': ts('#09885a', undefinedStyle), + 'keyword': ts('#0000ff', undefinedStyle) }); }); @@ -167,13 +167,13 @@ suite('Themes - TokenStyleResolving', () => { assert.equal(themeData.isLoaded, true); assertTokenStyles(themeData, { - [comments]: ts('#7ca668', undefinedStyle), - [variables]: ts('#9CDCFE', undefinedStyle), - [types]: ts('#4EC9B0', undefinedStyle), - [functions]: ts('#DCDCAA', undefinedStyle), - [strings]: ts('#ce9178', undefinedStyle), - [numbers]: ts('#b5cea8', undefinedStyle), - [keywords]: ts('#C586C0', undefinedStyle) + 'comment': ts('#7ca668', undefinedStyle), + 'variable': ts('#9CDCFE', undefinedStyle), + 'type': ts('#4EC9B0', undefinedStyle), + 'function': ts('#DCDCAA', undefinedStyle), + 'string': ts('#ce9178', undefinedStyle), + 'number': ts('#b5cea8', undefinedStyle), + 'keyword': ts('#C586C0', undefinedStyle) }); }); @@ -187,13 +187,13 @@ suite('Themes - TokenStyleResolving', () => { assert.equal(themeData.isLoaded, true); assertTokenStyles(themeData, { - [comments]: ts('#a57a4c', undefinedStyle), - [variables]: ts('#dc3958', undefinedStyle), - [types]: ts('#f06431', undefinedStyle), - [functions]: ts('#8ab1b0', undefinedStyle), - [strings]: ts('#889b4a', undefinedStyle), - [numbers]: ts('#f79a32', undefinedStyle), - [keywords]: ts('#98676a', undefinedStyle) + 'comment': ts('#a57a4c', undefinedStyle), + 'variable': ts('#dc3958', undefinedStyle), + 'type': ts('#f06431', undefinedStyle), + 'function': ts('#8ab1b0', undefinedStyle), + 'string': ts('#889b4a', undefinedStyle), + 'number': ts('#f79a32', undefinedStyle), + 'keyword': ts('#98676a', undefinedStyle) }); }); @@ -207,13 +207,13 @@ suite('Themes - TokenStyleResolving', () => { assert.equal(themeData.isLoaded, true); assertTokenStyles(themeData, { - [comments]: ts('#384887', undefinedStyle), - [variables]: ts(undefined, unsetStyle), - [types]: ts('#ffeebb', { underline: true }), - [functions]: ts('#ddbb88', unsetStyle), - [strings]: ts('#22aa44', undefinedStyle), - [numbers]: ts('#f280d0', undefinedStyle), - [keywords]: ts('#225588', undefinedStyle) + 'comment': ts('#384887', undefinedStyle), + 'variable': ts(undefined, unsetStyle), + 'type': ts('#ffeebb', { underline: true }), + 'function': ts('#ddbb88', unsetStyle), + 'string': ts('#22aa44', undefinedStyle), + 'number': ts('#f280d0', undefinedStyle), + 'keyword': ts('#225588', undefinedStyle) }); }); @@ -301,8 +301,8 @@ suite('Themes - TokenStyleResolving', () => { const themeData = ColorThemeData.createLoadedEmptyTheme('test', 'test'); themeData.setCustomColors({ 'editor.foreground': '#000000' }); themeData.setCustomTokenStyleRules({ - 'types': '#ff0000', - 'classes': { foreground: '#0000ff', fontStyle: 'italic' }, + 'type': '#ff0000', + 'class': { foreground: '#0000ff', fontStyle: 'italic' }, '*.static': { fontStyle: 'bold' }, '*.declaration': { fontStyle: 'italic' }, '*.async.static': { fontStyle: 'italic underline' }, @@ -310,14 +310,14 @@ suite('Themes - TokenStyleResolving', () => { }); assertTokenStyles(themeData, { - 'types': ts('#ff0000', undefinedStyle), - 'types.static': ts('#ff0000', { bold: true }), - 'types.static.declaration': ts('#ff0000', { bold: true, italic: true }), - 'classes': ts('#0000ff', { italic: true }), - 'classes.static.declaration': ts('#0000ff', { bold: true, italic: true }), - 'classes.declaration': ts('#0000ff', { italic: true }), - 'classes.declaration.async': ts('#000fff', { underline: true, italic: false }), - 'classes.declaration.async.static': ts('#000fff', { italic: true, underline: true, bold: true }), + 'type': ts('#ff0000', undefinedStyle), + 'type.static': ts('#ff0000', { bold: true }), + 'type.static.declaration': ts('#ff0000', { bold: true, italic: true }), + 'class': ts('#0000ff', { italic: true }), + 'class.static.declaration': ts('#0000ff', { bold: true, italic: true }), + 'class.declaration': ts('#0000ff', { italic: true }), + 'class.declaration.async': ts('#000fff', { underline: true, italic: false }), + 'class.declaration.async.static': ts('#000fff', { italic: true, underline: true, bold: true }), }); }); From d386b49dee6267238389c0a616af2be89726a4e3 Mon Sep 17 00:00:00 2001 From: isidor Date: Fri, 6 Dec 2019 12:26:31 +0100 Subject: [PATCH 249/255] fixes #85148 --- .../ui/codiconLabel/codicon/codicon.css | 1 - .../contrib/debug/browser/breakpointsView.ts | 19 ---------------- .../browser/debugCallStackContribution.ts | 7 +++--- .../browser/media/debug.contribution.css | 22 +++---------------- .../debug/browser/media/debugViewlet.css | 4 ---- 5 files changed, 6 insertions(+), 47 deletions(-) diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css index 2e259eda4f2..162038bd8e5 100644 --- a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css +++ b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css @@ -396,7 +396,6 @@ .codicon-debug-breakpoint-function:before { content: "\eb88" } .codicon-debug-breakpoint-function-disabled:before { content: "\eb88" } .codicon-debug-breakpoint-stackframe-active:before { content: "\eb89" } -.codicon-debug-breakpoint-stackframe-dot:before { content: "\eb8a" } .codicon-debug-breakpoint-stackframe:before { content: "\eb8b" } .codicon-debug-breakpoint-stackframe-focused:before { content: "\eb8b" } .codicon-debug-breakpoint-unsupported:before { content: "\eb8c" } diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index 8d19e351841..7f277994bb7 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -713,25 +713,6 @@ export function getBreakpointMessageAndClassName(debugService: IDebugService, br }; } - const focusedThread = debugService.getViewModel().focusedThread; - if (focusedThread) { - const callStack = focusedThread ? focusedThread.getCallStack() : undefined; - const topStackFrame = callStack ? callStack[0] : undefined; - if (topStackFrame && topStackFrame.source.uri.toString() === breakpoint.uri.toString() && topStackFrame.range.startLineNumber === breakpoint.lineNumber) { - if (topStackFrame.range.startColumn === breakpoint.column) { - return { - className: 'codicon-debug-breakpoint-stackframe-dot', - message: breakpoint.message || nls.localize('breakpoint', "Breakpoint") - }; - } else if (breakpoint.column === undefined) { - return { - className: 'codicon-debug-breakpoint', - message: breakpoint.message || nls.localize('breakpoint', "Breakpoint") - }; - } - } - } - return { className: 'codicon-debug-breakpoint', message: breakpoint.message || nls.localize('breakpoint', "Breakpoint") diff --git a/src/vs/workbench/contrib/debug/browser/debugCallStackContribution.ts b/src/vs/workbench/contrib/debug/browser/debugCallStackContribution.ts index ee22aee0239..dc262b161e6 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCallStackContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCallStackContribution.ts @@ -187,8 +187,8 @@ registerThemingParticipant((theme, collector) => { .monaco-workbench .codicon-debug-breakpoint-data, .monaco-workbench .codicon-debug-breakpoint-unsupported, .monaco-workbench .codicon-debug-hint:not([class*='codicon-debug-breakpoint']), - .monaco-workbench .codicon-debug-breakpoint-stackframe-dot, - .monaco-workbench .codicon-debug-breakpoint.codicon-debug-breakpoint-stackframe-focused::after { + .monaco-workbench .codicon-debug-breakpoint.codicon-debug-breakpoint-stackframe-focused::after, + .monaco-workbench .codicon-debug-breakpoint.codicon-debug-breakpoint-stackframe::after { color: ${debugIconBreakpointColor} !important; } `); @@ -215,8 +215,7 @@ registerThemingParticipant((theme, collector) => { const debugIconBreakpointCurrentStackframeForegroundColor = theme.getColor(debugIconBreakpointCurrentStackframeForeground); if (debugIconBreakpointCurrentStackframeForegroundColor) { collector.addRule(` - .monaco-workbench .codicon-debug-breakpoint-stackframe, - .monaco-workbench .codicon-debug-breakpoint-stackframe-dot::after { + .monaco-workbench .codicon-debug-breakpoint-stackframe { color: ${debugIconBreakpointCurrentStackframeForegroundColor} !important; } `); diff --git a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css index 215fb619865..858d8cbacd7 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css +++ b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css @@ -16,26 +16,10 @@ align-items: center; } -/* overlapped icons */ -.inline-breakpoint-widget.codicon-debug-breakpoint-stackframe-dot::after { - position: absolute; - top: 0; - left: 0; - bottom: 0; - margin: auto; - display: table; -} - -.codicon-debug-breakpoint.codicon-debug-breakpoint-stackframe-focused::after { - position: absolute; -} - -.inline-breakpoint-widget.codicon-debug-breakpoint-stackframe-dot::after { - content: "\eb8b"; -} - -.codicon-debug-breakpoint.codicon-debug-breakpoint-stackframe-focused::after { +.codicon-debug-breakpoint.codicon-debug-breakpoint-stackframe-focused::after, +.codicon-debug-breakpoint.codicon-debug-breakpoint-stackframe::after { content: "\eb8a"; + position: absolute; } .monaco-editor .inline-breakpoint-widget.line-start { diff --git a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css index e40b50a0900..4ccff4262eb 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css @@ -374,10 +374,6 @@ justify-content: center; } -.debug-viewlet .debug-breakpoints .breakpoint > .codicon-debug-breakpoint-stackframe-dot::before { - content: "\ea71"; -} - .debug-viewlet .debug-breakpoints .breakpoint > .file-path { opacity: 0.7; font-size: 0.9em; From b2868cc01cba92ed342400e23bd47e06622ee223 Mon Sep 17 00:00:00 2001 From: Pine Wu Date: Fri, 6 Dec 2019 11:24:11 -0800 Subject: [PATCH 250/255] Fix #85981 --- extensions/css-language-features/server/package.json | 2 +- extensions/css-language-features/server/yarn.lock | 8 ++++---- extensions/html-language-features/server/package.json | 2 +- extensions/html-language-features/server/yarn.lock | 8 ++++---- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/extensions/css-language-features/server/package.json b/extensions/css-language-features/server/package.json index 5ff5b49c5f3..66c7e087dcc 100644 --- a/extensions/css-language-features/server/package.json +++ b/extensions/css-language-features/server/package.json @@ -9,7 +9,7 @@ }, "main": "./out/cssServerMain", "dependencies": { - "vscode-css-languageservice": "^4.0.3-next.23", + "vscode-css-languageservice": "^4.0.3-next.24", "vscode-languageserver": "^6.0.0-next.3" }, "devDependencies": { diff --git a/extensions/css-language-features/server/yarn.lock b/extensions/css-language-features/server/yarn.lock index 6191a8ffcd5..9196af4c058 100644 --- a/extensions/css-language-features/server/yarn.lock +++ b/extensions/css-language-features/server/yarn.lock @@ -781,10 +781,10 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" -vscode-css-languageservice@^4.0.3-next.23: - version "4.0.3-next.23" - resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-4.0.3-next.23.tgz#b7a835c317a6f0b96ec104e3ce168770f5f0d4ff" - integrity sha512-DmMXBzTd3uYnVMffNlcp+Sy4qdzeE+UG5yivo/5Y1f/Qr//4FyssH0eCZ7K9Vf/9DMKYf9J3HeCNZSTq4EzMrg== +vscode-css-languageservice@^4.0.3-next.24: + version "4.0.3-next.24" + resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-4.0.3-next.24.tgz#6190fb5af4621b7efc1a0772c1a178d996ffb42d" + integrity sha512-7CPBmaQnvkgL7MXZK9vt5R5CwFaQ9TCqWt9aCi9dGm1g0uAgFOwk8+K5+fV2pasI+aoOvb/kLcFjxSSRD03KdA== dependencies: vscode-languageserver-textdocument "^1.0.0-next.4" vscode-languageserver-types "^3.15.0-next.6" diff --git a/extensions/html-language-features/server/package.json b/extensions/html-language-features/server/package.json index 27661b072bd..7060461f908 100644 --- a/extensions/html-language-features/server/package.json +++ b/extensions/html-language-features/server/package.json @@ -9,7 +9,7 @@ }, "main": "./out/htmlServerMain", "dependencies": { - "vscode-css-languageservice": "^4.0.3-next.23", + "vscode-css-languageservice": "^4.0.3-next.24", "vscode-html-languageservice": "^3.0.4-next.11", "vscode-languageserver": "^6.0.0-next.3", "vscode-nls": "^4.1.1", diff --git a/extensions/html-language-features/server/yarn.lock b/extensions/html-language-features/server/yarn.lock index 395953969f4..c42d15d833e 100644 --- a/extensions/html-language-features/server/yarn.lock +++ b/extensions/html-language-features/server/yarn.lock @@ -611,10 +611,10 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" -vscode-css-languageservice@^4.0.3-next.23: - version "4.0.3-next.23" - resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-4.0.3-next.23.tgz#b7a835c317a6f0b96ec104e3ce168770f5f0d4ff" - integrity sha512-DmMXBzTd3uYnVMffNlcp+Sy4qdzeE+UG5yivo/5Y1f/Qr//4FyssH0eCZ7K9Vf/9DMKYf9J3HeCNZSTq4EzMrg== +vscode-css-languageservice@^4.0.3-next.24: + version "4.0.3-next.24" + resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-4.0.3-next.24.tgz#6190fb5af4621b7efc1a0772c1a178d996ffb42d" + integrity sha512-7CPBmaQnvkgL7MXZK9vt5R5CwFaQ9TCqWt9aCi9dGm1g0uAgFOwk8+K5+fV2pasI+aoOvb/kLcFjxSSRD03KdA== dependencies: vscode-languageserver-textdocument "^1.0.0-next.4" vscode-languageserver-types "^3.15.0-next.6" From 87c2e0be5d7359ce110c4c9ec9f6df71be996f8a Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 6 Dec 2019 11:45:17 -0800 Subject: [PATCH 251/255] Make sure we set the group of the newly created webview during webview move operations Fixes #86407 --- .../contrib/customEditor/browser/customEditorInput.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index c9394ba4e92..ab6ddeb66df 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -156,15 +156,17 @@ export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { }); } - public handleMove(_groupId: GroupIdentifier, uri: URI, options?: ITextEditorOptions): IEditorInput | undefined { + public handleMove(groupId: GroupIdentifier, uri: URI, options?: ITextEditorOptions): IEditorInput | undefined { const editorInfo = this.customEditorService.getCustomEditor(this.viewType); if (editorInfo?.matches(uri)) { const webview = assertIsDefined(this.takeOwnershipOfWebview()); - return this.instantiationService.createInstance(CustomFileEditorInput, + const newInput = this.instantiationService.createInstance(CustomFileEditorInput, uri, this.viewType, generateUuid(), new Lazy(() => webview)); + newInput.updateGroup(groupId); + return newInput; } return undefined; } From 30b85234acf6783186c48df64d8e9a8190acaf4c Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Fri, 6 Dec 2019 13:39:35 -0800 Subject: [PATCH 252/255] bump version to 1.42.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 751de76f58a..0fcecaa745a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "code-oss-dev", - "version": "1.41.0", + "version": "1.42.0", "distro": "17f1b806c349d58f96b4aef97ae59d836e2c5605", "author": { "name": "Microsoft Corporation" From 0403a10885f2ce11c17c7222a70f8c73df7c3ba6 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sun, 24 Nov 2019 13:22:04 -0800 Subject: [PATCH 253/255] Don't restart the EH when the first workspace folder changes For #69335 --- .../browser/relauncher.contribution.ts | 99 ++----------------- 1 file changed, 8 insertions(+), 91 deletions(-) diff --git a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts index a152b50423d..4ef39c8a040 100644 --- a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts +++ b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts @@ -3,23 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IDisposable, dispose, Disposable, toDisposable } from 'vs/base/common/lifecycle'; -import { IWorkbenchContributionsRegistry, IWorkbenchContribution, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { isMacintosh, isNative } from 'vs/base/common/platform'; +import { localize } from 'vs/nls'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { IProductService } from 'vs/platform/product/common/productService'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWindowsConfiguration } from 'vs/platform/windows/common/windows'; +import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { localize } from 'vs/nls'; -import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { RunOnceScheduler } from 'vs/base/common/async'; -import { URI } from 'vs/base/common/uri'; -import { isEqual } from 'vs/base/common/resources'; -import { isMacintosh, isNative } from 'vs/base/common/platform'; -import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IProductService } from 'vs/platform/product/common/productService'; interface IConfiguration extends IWindowsConfiguration { update: { mode: string; }; @@ -132,82 +126,5 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo } } -export class WorkspaceChangeExtHostRelauncher extends Disposable implements IWorkbenchContribution { - - private firstFolderResource?: URI; - private extensionHostRestarter: RunOnceScheduler; - - private onDidChangeWorkspaceFoldersUnbind: IDisposable | undefined; - - constructor( - @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @IExtensionService extensionService: IExtensionService, - @IHostService hostService: IHostService, - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService - ) { - super(); - - this.extensionHostRestarter = this._register(new RunOnceScheduler(() => { - if (!!environmentService.extensionTestsLocationURI) { - return; // no restart when in tests: see https://github.com/Microsoft/vscode/issues/66936 - } - - if (environmentService.configuration.remoteAuthority) { - hostService.reload(); // TODO@aeschli, workaround - } else if (isNative) { - extensionService.restartExtensionHost(); - } - }, 10)); - - this.contextService.getCompleteWorkspace() - .then(workspace => { - this.firstFolderResource = workspace.folders.length > 0 ? workspace.folders[0].uri : undefined; - this.handleWorkbenchState(); - this._register(this.contextService.onDidChangeWorkbenchState(() => setTimeout(() => this.handleWorkbenchState()))); - }); - - this._register(toDisposable(() => { - if (this.onDidChangeWorkspaceFoldersUnbind) { - this.onDidChangeWorkspaceFoldersUnbind.dispose(); - } - })); - } - - private handleWorkbenchState(): void { - - // React to folder changes when we are in workspace state - if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) { - - // Update our known first folder path if we entered workspace - const workspace = this.contextService.getWorkspace(); - this.firstFolderResource = workspace.folders.length > 0 ? workspace.folders[0].uri : undefined; - - // Install workspace folder listener - if (!this.onDidChangeWorkspaceFoldersUnbind) { - this.onDidChangeWorkspaceFoldersUnbind = this.contextService.onDidChangeWorkspaceFolders(() => this.onDidChangeWorkspaceFolders()); - } - } - - // Ignore the workspace folder changes in EMPTY or FOLDER state - else { - dispose(this.onDidChangeWorkspaceFoldersUnbind); - this.onDidChangeWorkspaceFoldersUnbind = undefined; - } - } - - private onDidChangeWorkspaceFolders(): void { - const workspace = this.contextService.getWorkspace(); - - // Restart extension host if first root folder changed (impact on deprecated workspace.rootPath API) - const newFirstFolderResource = workspace.folders.length > 0 ? workspace.folders[0].uri : undefined; - if (!isEqual(this.firstFolderResource, newFirstFolderResource)) { - this.firstFolderResource = newFirstFolderResource; - - this.extensionHostRestarter.schedule(); // buffer calls to extension host restart - } - } -} - const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchRegistry.registerWorkbenchContribution(SettingsChangeRelauncher, LifecyclePhase.Restored); -workbenchRegistry.registerWorkbenchContribution(WorkspaceChangeExtHostRelauncher, LifecyclePhase.Restored); From 5bc80f3ea031739c2f8796857221be6825b288da Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 6 Dec 2019 14:07:15 -0800 Subject: [PATCH 254/255] Make rootPath undefined in a multiroot workspace, #69335 --- src/vs/workbench/api/common/extHostWorkspace.ts | 5 +++++ .../test/electron-browser/api/extHostWorkspace.test.ts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/api/common/extHostWorkspace.ts b/src/vs/workbench/api/common/extHostWorkspace.ts index 4b5c0c1301f..99654eded85 100644 --- a/src/vs/workbench/api/common/extHostWorkspace.ts +++ b/src/vs/workbench/api/common/extHostWorkspace.ts @@ -339,6 +339,11 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac if (folders.length === 0) { return undefined; } + + if (folders.length > 1) { + return undefined; + } + // #54483 @Joh Why are we still using fsPath? return folders[0].uri.fsPath; } diff --git a/src/vs/workbench/test/electron-browser/api/extHostWorkspace.test.ts b/src/vs/workbench/test/electron-browser/api/extHostWorkspace.test.ts index c05a1d72fd8..d505968d588 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostWorkspace.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostWorkspace.test.ts @@ -120,7 +120,7 @@ suite('ExtHostWorkspace', function () { assert.equal(ws.getPath(), undefined); ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.file('Folder'), 0), aWorkspaceFolderData(URI.file('Another/Folder'), 1)] }, new NullLogService()); - assert.equal(ws.getPath()!.replace(/\\/g, '/'), '/Folder'); + assert.equal(ws.getPath(), undefined); ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.file('/Folder'), 0)] }, new NullLogService()); assert.equal(ws.getPath()!.replace(/\\/g, '/'), '/Folder'); From a416c77e56ef0314ae00633faa04878151610de8 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 6 Dec 2019 14:32:27 -0800 Subject: [PATCH 255/255] Fix rootPath test --- .../vscode-api-tests/src/workspace-tests/workspace.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/vscode-api-tests/src/workspace-tests/workspace.test.ts b/extensions/vscode-api-tests/src/workspace-tests/workspace.test.ts index f018f581c42..729dd8f14d5 100644 --- a/extensions/vscode-api-tests/src/workspace-tests/workspace.test.ts +++ b/extensions/vscode-api-tests/src/workspace-tests/workspace.test.ts @@ -13,7 +13,7 @@ suite('workspace-namespace', () => { teardown(closeAllEditors); test('rootPath', () => { - assert.ok(pathEquals(vscode.workspace.rootPath!, join(__dirname, '../../testWorkspace'))); + assert.equal(vscode.workspace.rootPath, undefined); }); test('workspaceFile', () => {