From c7f17d533304872d1ea77dbbfed0b8b8ef250f0a Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 21 Jun 2018 17:11:49 -0700 Subject: [PATCH 001/228] Add 'contributedByExtension' field to IConfigurationNode and ISettingsGroup. We want to keep track of which setting groups come from extensions, so they can be displayed separately in the settings editor --- .../common/configurationRegistry.ts | 1 + .../preferences/browser/settingsEditor2.ts | 10 ++++--- .../preferences/browser/settingsLayout.ts | 5 ---- .../parts/preferences/browser/settingsTree.ts | 27 +++++++++++++++++-- .../common/configurationExtensionPoint.ts | 3 ++- .../preferences/common/preferences.ts | 1 + .../preferences/common/preferencesModels.ts | 7 ++--- 7 files changed, 40 insertions(+), 14 deletions(-) diff --git a/src/vs/platform/configuration/common/configurationRegistry.ts b/src/vs/platform/configuration/common/configurationRegistry.ts index 30d8205f48d..716575efa05 100644 --- a/src/vs/platform/configuration/common/configurationRegistry.ts +++ b/src/vs/platform/configuration/common/configurationRegistry.ts @@ -84,6 +84,7 @@ export interface IConfigurationNode { allOf?: IConfigurationNode[]; overridable?: boolean; scope?: ConfigurationScope; + contributedByExtension?: boolean; } export interface IDefaultConfigurationExtension { diff --git a/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts b/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts index c024a63eb7f..7306522cc98 100644 --- a/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts @@ -6,6 +6,7 @@ import * as DOM from 'vs/base/browser/dom'; import { Button } from 'vs/base/browser/ui/button/button'; import * as arrays from 'vs/base/common/arrays'; +import * as collections from 'vs/base/common/collections'; import { Delayer, ThrottledDelayer } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Color } from 'vs/base/common/color'; @@ -30,7 +31,7 @@ import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { EditorOptions, IEditor } from 'vs/workbench/common/editor'; import { SearchWidget, SettingsTarget, SettingsTargetsWidget } from 'vs/workbench/parts/preferences/browser/preferencesWidgets'; import { tocData, commonlyUsedData } from 'vs/workbench/parts/preferences/browser/settingsLayout'; -import { ISettingsEditorViewState, SearchResultIdx, SearchResultModel, SettingsAccessibilityProvider, SettingsDataSource, SettingsRenderer, SettingsTreeController, SettingsTreeElement, SettingsTreeFilter, SettingsTreeModel, SettingsTreeSettingElement, SettingsTreeGroupElement, resolveSettingsTree, NonExpandableTree } from 'vs/workbench/parts/preferences/browser/settingsTree'; +import { ISettingsEditorViewState, SearchResultIdx, SearchResultModel, SettingsAccessibilityProvider, SettingsDataSource, SettingsRenderer, SettingsTreeController, SettingsTreeElement, SettingsTreeFilter, SettingsTreeModel, SettingsTreeSettingElement, SettingsTreeGroupElement, resolveSettingsTree, NonExpandableTree, resolveExtensionsSettings } from 'vs/workbench/parts/preferences/browser/settingsTree'; import { TOCDataSource, TOCRenderer, TOCTreeModel } from 'vs/workbench/parts/preferences/browser/tocTree'; import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_SEARCH_FOCUS, IPreferencesSearchService, ISearchProvider } from 'vs/workbench/parts/preferences/common/preferences'; import { IPreferencesService, ISearchResult, ISettingsEditorModel } from 'vs/workbench/services/preferences/common/preferences'; @@ -480,10 +481,13 @@ export class SettingsEditor2 extends BaseEditor { private onConfigUpdate(): TPromise { const groups = this.defaultSettingsEditorModel.settingsGroups.slice(1); // Without commonlyUsed - const resolvedSettingsRoot = resolveSettingsTree(tocData, groups); - const commonlyUsed = resolveSettingsTree(commonlyUsedData, groups); + const dividedGroups = collections.groupBy(groups, g => g.contributedByExtension ? 'extension' : 'core'); + const resolvedSettingsRoot = resolveSettingsTree(tocData, dividedGroups.core); + const commonlyUsed = resolveSettingsTree(commonlyUsedData, dividedGroups.core); resolvedSettingsRoot.children.unshift(commonlyUsed); + resolvedSettingsRoot.children.push(resolveExtensionsSettings(dividedGroups.extension || [])); + if (this.settingsTreeModel) { this.settingsTreeModel.update(resolvedSettingsRoot); } else { diff --git a/src/vs/workbench/parts/preferences/browser/settingsLayout.ts b/src/vs/workbench/parts/preferences/browser/settingsLayout.ts index e4c6a4620e8..65a5ec6eda2 100644 --- a/src/vs/workbench/parts/preferences/browser/settingsLayout.ts +++ b/src/vs/workbench/parts/preferences/browser/settingsLayout.ts @@ -184,11 +184,6 @@ export const tocData: ITOCEntry = { settings: ['telemetry.*'] } ] - }, - { - id: 'extensions', - label: localize('extensions', "Extensions"), - settings: ['*'] } ] }; diff --git a/src/vs/workbench/parts/preferences/browser/settingsTree.ts b/src/vs/workbench/parts/preferences/browser/settingsTree.ts index 0802bb38bb5..10d0c6b2e19 100644 --- a/src/vs/workbench/parts/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/parts/preferences/browser/settingsTree.ts @@ -186,8 +186,31 @@ function inspectSetting(key: string, target: SettingsTarget, configurationServic return { isConfigured, inspected, targetSelector }; } -export function resolveSettingsTree(tocData: ITOCEntry, settingsGroups: ISettingsGroup[]): ITOCEntry { - return _resolveSettingsTree(tocData, getFlatSettings(settingsGroups)); +export function resolveSettingsTree(tocData: ITOCEntry, coreSettingsGroups: ISettingsGroup[]): ITOCEntry { + return _resolveSettingsTree(tocData, getFlatSettings(coreSettingsGroups)); +} + +export function resolveExtensionsSettings(groups: ISettingsGroup[]): ITOCEntry { + const settingsGroupToEntry = (group: ISettingsGroup) => { + const flatSettings = arrays.flatten( + group.sections.map(section => section.settings)); + + return { + id: group.id, + label: group.title, + settings: flatSettings + }; + }; + + const extGroups = groups + .sort((a, b) => a.title.localeCompare(b.title)) + .map(g => settingsGroupToEntry(g)); + + return { + id: 'extensions', + label: localize('extensions', "Extensions"), + children: extGroups + }; } function _resolveSettingsTree(tocData: ITOCEntry, allSettings: Set): ITOCEntry { diff --git a/src/vs/workbench/services/configuration/common/configurationExtensionPoint.ts b/src/vs/workbench/services/configuration/common/configurationExtensionPoint.ts index 5bb46155fc9..6705a89acda 100644 --- a/src/vs/workbench/services/configuration/common/configurationExtensionPoint.ts +++ b/src/vs/workbench/services/configuration/common/configurationExtensionPoint.ts @@ -106,7 +106,8 @@ configurationExtPoint.setHandler(extensions => { validateProperties(configuration, extension); - configuration.id = extension.description.uuid || extension.description.id; + configuration.id = node.id || extension.description.uuid || extension.description.id; + configuration.contributedByExtension = true; configuration.title = configuration.title || extension.description.displayName || extension.description.id; configurations.push(configuration); } diff --git a/src/vs/workbench/services/preferences/common/preferences.ts b/src/vs/workbench/services/preferences/common/preferences.ts index 82028544ee6..8c649215708 100644 --- a/src/vs/workbench/services/preferences/common/preferences.ts +++ b/src/vs/workbench/services/preferences/common/preferences.ts @@ -25,6 +25,7 @@ export interface ISettingsGroup { title: string; titleRange: IRange; sections: ISettingsSection[]; + contributedByExtension: boolean; } export interface ISettingsSection { diff --git a/src/vs/workbench/services/preferences/common/preferencesModels.ts b/src/vs/workbench/services/preferences/common/preferencesModels.ts index a20abf46e45..4f3ca88dfe0 100644 --- a/src/vs/workbench/services/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/services/preferences/common/preferencesModels.ts @@ -189,7 +189,8 @@ export class SettingsEditorModel extends AbstractSettingsModel implements ISetti settings: filteredSettings }], title: modelGroup.title, - titleRange: modelGroup.titleRange + titleRange: modelGroup.titleRange, + contributedByExtension: !!modelGroup.contributedByExtension }; } @@ -503,7 +504,7 @@ export class DefaultSettings extends Disposable { if (!settingsGroup) { settingsGroup = result.filter(g => g.title === title)[0]; if (!settingsGroup) { - settingsGroup = { sections: [{ settings: [] }], id: config.id, title: title, titleRange: null, range: null }; + settingsGroup = { sections: [{ settings: [] }], id: config.id, title: title, titleRange: null, range: null, contributedByExtension: !!config.contributedByExtension }; result.push(settingsGroup); } } else { @@ -512,7 +513,7 @@ export class DefaultSettings extends Disposable { } if (config.properties) { if (!settingsGroup) { - settingsGroup = { sections: [{ settings: [] }], id: config.id, title: config.id, titleRange: null, range: null }; + settingsGroup = { sections: [{ settings: [] }], id: config.id, title: config.id, titleRange: null, range: null, contributedByExtension: !!config.contributedByExtension }; result.push(settingsGroup); } const configurationSettings: ISetting[] = [...settingsGroup.sections[settingsGroup.sections.length - 1].settings, ...this.parseSettings(config.properties)]; From 605ba838641311ac321d1573fcfbcad2280aca0c Mon Sep 17 00:00:00 2001 From: Leonardo Braga Date: Sat, 16 Jun 2018 17:53:51 -0400 Subject: [PATCH 002/228] Fixes stuck hover when manually triggered and "editor.hover" is false - Adds support to toggle hover without reloading the editor --- src/vs/editor/contrib/hover/hover.ts | 72 ++++++++++++++++++++-------- 1 file changed, 53 insertions(+), 19 deletions(-) diff --git a/src/vs/editor/contrib/hover/hover.ts b/src/vs/editor/contrib/hover/hover.ts index ddbc96953b3..6296a0e50a4 100644 --- a/src/vs/editor/contrib/hover/hover.ts +++ b/src/vs/editor/contrib/hover/hover.ts @@ -13,7 +13,8 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IModeService } from 'vs/editor/common/services/modeService'; import { Range } from 'vs/editor/common/core/range'; -import * as editorCommon from 'vs/editor/common/editorCommon'; +import { IEditorContribution, IScrollEvent } from 'vs/editor/common/editorCommon'; +import { IConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions'; import { registerEditorAction, registerEditorContribution, ServicesAccessor, EditorAction } from 'vs/editor/browser/editorExtensions'; import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { ModesContentHoverWidget } from './modesContentHover'; @@ -26,12 +27,12 @@ import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer'; import { IEmptyContentData } from 'vs/editor/browser/controller/mouseTarget'; import { HoverStartMode } from 'vs/editor/contrib/hover/hoverOperation'; -export class ModesHoverController implements editorCommon.IEditorContribution { +export class ModesHoverController implements IEditorContribution { private static readonly ID = 'editor.contrib.hover'; - private _editor: ICodeEditor; private _toUnhook: IDisposable[]; + private _didChangeConfigurationHandler: IDisposable; private _contentWidget: ModesContentHoverWidget; private _glyphWidget: ModesGlyphHoverWidget; @@ -52,35 +53,54 @@ export class ModesHoverController implements editorCommon.IEditorContribution { private _isMouseDown: boolean; private _hoverClicked: boolean; + private _isHoverEnabled: boolean; static get(editor: ICodeEditor): ModesHoverController { return editor.getContribution(ModesHoverController.ID); } - constructor(editor: ICodeEditor, + constructor(private readonly _editor: ICodeEditor, @IOpenerService private readonly _openerService: IOpenerService, @IModeService private readonly _modeService: IModeService, @IThemeService private readonly _themeService: IThemeService ) { - this._editor = editor; - this._toUnhook = []; - this._isMouseDown = false; - if (editor.getConfiguration().contribInfo.hover) { + this._isMouseDown = false; + this._hoverClicked = false; + + this._hookEvents(); + + this._didChangeConfigurationHandler = this._editor.onDidChangeConfiguration((e: IConfigurationChangedEvent) => { + if (e.contribInfo) { + this._hideWidgets(); + this._unhookEvents(); + this._hookEvents(); + } + }); + } + + private _hookEvents(): void { + const hideWidgetsEventHandler = () => this._hideWidgets(); + + this._isHoverEnabled = this._editor.getConfiguration().contribInfo.hover.enabled; + if (this._isHoverEnabled) { this._toUnhook.push(this._editor.onMouseDown((e: IEditorMouseEvent) => this._onEditorMouseDown(e))); this._toUnhook.push(this._editor.onMouseUp((e: IEditorMouseEvent) => this._onEditorMouseUp(e))); this._toUnhook.push(this._editor.onMouseMove((e: IEditorMouseEvent) => this._onEditorMouseMove(e))); - this._toUnhook.push(this._editor.onMouseLeave((e: IEditorMouseEvent) => this._hideWidgets())); this._toUnhook.push(this._editor.onKeyDown((e: IKeyboardEvent) => this._onKeyDown(e))); - this._toUnhook.push(this._editor.onDidChangeModel(() => this._hideWidgets())); this._toUnhook.push(this._editor.onDidChangeModelDecorations(() => this._onModelDecorationsChanged())); - this._toUnhook.push(this._editor.onDidScrollChange((e) => { - if (e.scrollTopChanged || e.scrollLeftChanged) { - this._hideWidgets(); - } - })); + } else { + this._toUnhook.push(this._editor.onMouseMove(hideWidgetsEventHandler)); } + + this._toUnhook.push(this._editor.onMouseLeave(hideWidgetsEventHandler)); + this._toUnhook.push(this._editor.onDidChangeModel(hideWidgetsEventHandler)); + this._toUnhook.push(this._editor.onDidScrollChange((e: IScrollEvent) => this._onEditorScrollChanged(e))); + } + + private _unhookEvents(): void { + this._toUnhook = dispose(this._toUnhook); } private _onModelDecorationsChanged(): void { @@ -88,6 +108,12 @@ export class ModesHoverController implements editorCommon.IEditorContribution { this.glyphWidget.onModelDecorationsChanged(); } + private _onEditorScrollChanged(e: IScrollEvent): void { + if (e.scrollTopChanged || e.scrollLeftChanged) { + this._hideWidgets(); + } + } + private _onEditorMouseDown(mouseEvent: IEditorMouseEvent): void { this._isMouseDown = true; @@ -142,12 +168,18 @@ export class ModesHoverController implements editorCommon.IEditorContribution { } } - if (this._editor.getConfiguration().contribInfo.hover && targetType === MouseTargetType.CONTENT_TEXT) { + if (targetType === MouseTargetType.CONTENT_TEXT) { this.glyphWidget.hide(); - this.contentWidget.startShowingAt(mouseEvent.target.range, HoverStartMode.Delayed, false); + + if (this._isHoverEnabled) { + this.contentWidget.startShowingAt(mouseEvent.target.range, HoverStartMode.Delayed, false); + } } else if (targetType === MouseTargetType.GUTTER_GLYPH_MARGIN) { this.contentWidget.hide(); - this.glyphWidget.startShowingAt(mouseEvent.target.position.lineNumber); + + if (this._isHoverEnabled) { + this.glyphWidget.startShowingAt(mouseEvent.target.position.lineNumber); + } } else { this._hideWidgets(); } @@ -184,7 +216,9 @@ export class ModesHoverController implements editorCommon.IEditorContribution { } public dispose(): void { - this._toUnhook = dispose(this._toUnhook); + this._unhookEvents(); + this._didChangeConfigurationHandler.dispose(); + if (this._glyphWidget) { this._glyphWidget.dispose(); this._glyphWidget = null; From 66a1bb7fe8b98b4e7e78a0154617778ecc16543d Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 25 Jun 2018 10:05:36 +0200 Subject: [PATCH 003/228] fix #51769 --- src/vs/editor/contrib/snippet/snippetParser.ts | 2 +- src/vs/editor/contrib/snippet/test/snippetParser.test.ts | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/snippet/snippetParser.ts b/src/vs/editor/contrib/snippet/snippetParser.ts index 7ab0d57eafe..35e163ea4ff 100644 --- a/src/vs/editor/contrib/snippet/snippetParser.ts +++ b/src/vs/editor/contrib/snippet/snippetParser.ts @@ -334,7 +334,7 @@ export class Transform extends Marker { } toTextmateString(): string { - return `/${Text.escape(this.regexp.source)}/${this.children.map(c => c.toTextmateString())}/${this.regexp.ignoreCase ? 'i' : ''}`; + return `/${Text.escape(this.regexp.source)}/${this.children.map(c => c.toTextmateString())}/${(this.regexp.ignoreCase ? 'i' : '') + (this.regexp.global ? 'g' : '')}`; } clone(): Transform { diff --git a/src/vs/editor/contrib/snippet/test/snippetParser.test.ts b/src/vs/editor/contrib/snippet/test/snippetParser.test.ts index 5cae847a726..b3ab4b6debe 100644 --- a/src/vs/editor/contrib/snippet/test/snippetParser.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetParser.test.ts @@ -720,4 +720,11 @@ suite('SnippetParser', () => { const snippet = new SnippetParser().parse('${TM_DIRECTORY/.*src[\\/](.*)/$1/}'); assertMarker(snippet, Variable); }); + + test('Variable transformation doesn\'t work if undefined variables are used in the same snippet #51769', function () { + let transform = new Transform(); + transform.appendChild(new Text('bar')); + transform.regexp = new RegExp('foo', 'gi'); + assert.equal(transform.toTextmateString(), '/foo/bar/ig'); + }); }); From 5d6156a0f8b68fe7e9429facbcfaa7c061a8b3e3 Mon Sep 17 00:00:00 2001 From: Ramya Rao Date: Mon, 25 Jun 2018 01:14:14 -0700 Subject: [PATCH 004/228] Show lang pack availability msg in English as well (#52706) --- src/vs/platform/node/minimalTranslations.ts | 4 ++-- .../localizations.contribution.ts | 21 +++++++++++++------ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/vs/platform/node/minimalTranslations.ts b/src/vs/platform/node/minimalTranslations.ts index 25a1de9e412..42e71e0a725 100644 --- a/src/vs/platform/node/minimalTranslations.ts +++ b/src/vs/platform/node/minimalTranslations.ts @@ -9,9 +9,9 @@ import { localize } from 'vs/nls'; // So that they are available for VS Code to use without downloading the entire language pack. export const minimumTranslatedStrings = { - showLanguagePackExtensions: localize('showLanguagePackExtensions', "VS Code is available in {0}. Search for language packs in the Marketplace to get started."), + showLanguagePackExtensions: localize('showLanguagePackExtensions', "Search language packs in the Marketplace to change the display language to {0}."), searchMarketplace: localize('searchMarketplace', "Search Marketplace"), - installAndRestartMessage: localize('installAndRestartMessage', "VS Code is available in {0}. Please install the language pack to change the display language."), + installAndRestartMessage: localize('installAndRestartMessage', "Install language pack to change the display language to {0}."), installAndRestart: localize('installAndRestart', "Install and Restart") }; diff --git a/src/vs/workbench/parts/localizations/electron-browser/localizations.contribution.ts b/src/vs/workbench/parts/localizations/electron-browser/localizations.contribution.ts index 6054a08b60b..e49400a3289 100644 --- a/src/vs/workbench/parts/localizations/electron-browser/localizations.contribution.ts +++ b/src/vs/workbench/parts/localizations/electron-browser/localizations.contribution.ts @@ -159,11 +159,21 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo TPromise.join([this.galleryService.getManifest(extensionToFetchTranslationsFrom), this.galleryService.getCoreTranslation(extensionToFetchTranslationsFrom, locale)]) .then(([manifest, translation]) => { const loc = manifest && manifest.contributes && manifest.contributes.localizations && manifest.contributes.localizations.filter(x => x.languageId.toLowerCase() === locale)[0]; + const languageName = loc ? (loc.languageName || locale) : locale; const languageDisplayName = loc ? (loc.localizedLanguageName || loc.languageName || locale) : locale; - const translations = { - ...minimumTranslatedStrings, - ...(translation && translation.contents ? translation.contents['vs/platform/node/minimalTranslations'] : {}) - }; + const translationsFromPack = translation && translation.contents ? translation.contents['vs/platform/node/minimalTranslations'] : {}; + const promptMessageKey = extensionToInstall ? 'installAndRestartMessage' : 'showLanguagePackExtensions'; + const useEnglish = !translationsFromPack[promptMessageKey]; + + const translations = {}; + Object.keys(minimumTranslatedStrings).forEach(key => { + if (!translationsFromPack[key] || useEnglish) { + translations[key] = minimumTranslatedStrings[key].replace('{0}', languageName); + } else { + translations[key] = `${translationsFromPack[key].replace('{0}', languageDisplayName)} (${minimumTranslatedStrings[key].replace('{0}', languageName)})`; + } + }); + const logUserReaction = (userReaction: string) => { /* __GDPR__ "languagePackSuggestion:popup" : { @@ -195,8 +205,7 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo } }; - const promptMessage = translations[extensionToInstall ? 'installAndRestartMessage' : 'showLanguagePackExtensions'] - .replace('{0}', languageDisplayName); + const promptMessage = translations[promptMessageKey]; this.notificationService.prompt( Severity.Info, From 26b1f144211d1dffe204570799353228f7026bb2 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 25 Jun 2018 11:49:11 +0200 Subject: [PATCH 005/228] ipc: log signal also when process crashes --- src/vs/base/parts/ipc/node/ipc.cp.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/base/parts/ipc/node/ipc.cp.ts b/src/vs/base/parts/ipc/node/ipc.cp.ts index b59747adc40..40d8fb8ff6b 100644 --- a/src/vs/base/parts/ipc/node/ipc.cp.ts +++ b/src/vs/base/parts/ipc/node/ipc.cp.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ChildProcess, fork } from 'child_process'; +import { ChildProcess, fork, ForkOptions } from 'child_process'; import { IDisposable } from 'vs/base/common/lifecycle'; import { TPromise } from 'vs/base/common/winjs.base'; import { Delayer } from 'vs/base/common/async'; @@ -125,7 +125,7 @@ export class Client implements IChannelClient, IDisposable { private get client(): IPCClient { if (!this._client) { const args = this.options && this.options.args ? this.options.args : []; - const forkOpts = Object.create(null); + const forkOpts: ForkOptions = Object.create(null); forkOpts.env = assign(deepClone(process.env), { 'VSCODE_PARENT_PID': String(process.pid) }); @@ -183,7 +183,7 @@ export class Client implements IChannelClient, IDisposable { } if (code !== 0 && signal !== 'SIGTERM') { - console.warn('IPC "' + this.options.serverName + '" crashed with exit code ' + code); + console.warn('IPC "' + this.options.serverName + '" crashed with exit code ' + code + ' and signal ' + signal); this.disposeDelayer.cancel(); this.disposeClient(); } From 80e64972b7bb6602484d3eebb9609728e2005575 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 25 Jun 2018 12:13:24 +0200 Subject: [PATCH 006/228] fix #52491 --- src/vs/workbench/common/editor.ts | 4 ---- .../services/preferences/common/preferencesEditorInput.ts | 4 ---- 2 files changed, 8 deletions(-) diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index d19a0249859..59e35f9be53 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -625,10 +625,6 @@ export class SideBySideEditorInput extends EditorInput { return this.description; } - public supportsSplitEditor(): boolean { - return false; - } - public matches(otherInput: any): boolean { if (super.matches(otherInput) === true) { return true; diff --git a/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts b/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts index 6fb507a0656..b81ca559188 100644 --- a/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts +++ b/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts @@ -23,10 +23,6 @@ export class PreferencesEditorInput extends SideBySideEditorInput { return PreferencesEditorInput.ID; } - public supportsSplitEditor(): boolean { - return true; - } - public getTitle(verbosity: Verbosity): string { return this.master.getTitle(verbosity); } From 81f8c8bfb2644dfc816e991c00eca784a04e3210 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 25 Jun 2018 12:10:00 +0200 Subject: [PATCH 007/228] show snippets that start with a trigger character, #37166 --- src/vs/editor/contrib/suggest/suggest.ts | 4 ++++ src/vs/editor/contrib/suggest/suggestModel.ts | 3 ++- .../electron-browser/snippetsService.ts | 11 ++++++++--- .../electron-browser/snippetsService.test.ts | 18 ++++++++++-------- 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/vs/editor/contrib/suggest/suggest.ts b/src/vs/editor/contrib/suggest/suggest.ts index 8e083b3ed76..566e1b12963 100644 --- a/src/vs/editor/contrib/suggest/suggest.ts +++ b/src/vs/editor/contrib/suggest/suggest.ts @@ -37,6 +37,10 @@ export type SnippetConfig = 'top' | 'bottom' | 'inline' | 'none'; let _snippetSuggestSupport: ISuggestSupport; +export function getSnippetSuggestSupport(): ISuggestSupport { + return _snippetSuggestSupport; +} + export function setSnippetSuggestSupport(support: ISuggestSupport): ISuggestSupport { const old = _snippetSuggestSupport; _snippetSuggestSupport = support; diff --git a/src/vs/editor/contrib/suggest/suggestModel.ts b/src/vs/editor/contrib/suggest/suggestModel.ts index 417451c9a35..0032b4fdc52 100644 --- a/src/vs/editor/contrib/suggest/suggestModel.ts +++ b/src/vs/editor/contrib/suggest/suggestModel.ts @@ -18,7 +18,7 @@ import { Selection } from 'vs/editor/common/core/selection'; import { ITextModel, IWordAtPosition } from 'vs/editor/common/model'; import { ISuggestSupport, StandardTokenType, SuggestContext, SuggestRegistry, SuggestTriggerKind } from 'vs/editor/common/modes'; import { CompletionModel } from './completionModel'; -import { ISuggestionItem, getSuggestionComparator, provideSuggestionItems } from './suggest'; +import { ISuggestionItem, getSuggestionComparator, provideSuggestionItems, getSnippetSuggestSupport } from './suggest'; export interface ICancelEvent { readonly retrigger: boolean; @@ -180,6 +180,7 @@ export class SuggestModel implements IDisposable { let set = supportsByTriggerCharacter[ch]; if (!set) { set = supportsByTriggerCharacter[ch] = new Set(); + set.add(getSnippetSuggestSupport()); } set.add(support); } diff --git a/src/vs/workbench/parts/snippets/electron-browser/snippetsService.ts b/src/vs/workbench/parts/snippets/electron-browser/snippetsService.ts index ab2b652f3f5..eecdc73b271 100644 --- a/src/vs/workbench/parts/snippets/electron-browser/snippetsService.ts +++ b/src/vs/workbench/parts/snippets/electron-browser/snippetsService.ts @@ -6,7 +6,7 @@ import { localize } from 'vs/nls'; import { ITextModel } from 'vs/editor/common/model'; -import { ISuggestSupport, ISuggestResult, ISuggestion, LanguageId, SuggestionType, SnippetType } from 'vs/editor/common/modes'; +import { ISuggestSupport, ISuggestResult, ISuggestion, LanguageId, SuggestionType, SnippetType, SuggestContext } from 'vs/editor/common/modes'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { setSnippetSuggestSupport } from 'vs/editor/contrib/suggest/suggest'; import { IModeService } from 'vs/editor/common/services/modeService'; @@ -321,7 +321,7 @@ export class SnippetSuggestProvider implements ISuggestSupport { // } - provideCompletionItems(model: ITextModel, position: Position): Promise { + provideCompletionItems(model: ITextModel, position: Position, context: SuggestContext): Promise { const languageId = this._getLanguageIdAtPosition(model, position); return this._snippets.getSnippets(languageId).then(snippets => { @@ -337,7 +337,12 @@ export class SnippetSuggestProvider implements ISuggestSupport { let overwriteBefore = 0; let accetSnippet = true; - if (lowWordUntil.length > 0 && startsWith(lowPrefix, lowWordUntil)) { + if (typeof context.triggerCharacter === 'string') { + // cheap match on the trigger-character + overwriteBefore = context.triggerCharacter.length; + accetSnippet = startsWith(lowPrefix, context.triggerCharacter.toLowerCase()); + + } else if (lowWordUntil.length > 0 && startsWith(lowPrefix, lowWordUntil)) { // cheap match on the (none-empty) current word overwriteBefore = lowWordUntil.length; accetSnippet = true; diff --git a/src/vs/workbench/parts/snippets/test/electron-browser/snippetsService.test.ts b/src/vs/workbench/parts/snippets/test/electron-browser/snippetsService.test.ts index cd3123b3b6a..6c469038ba7 100644 --- a/src/vs/workbench/parts/snippets/test/electron-browser/snippetsService.test.ts +++ b/src/vs/workbench/parts/snippets/test/electron-browser/snippetsService.test.ts @@ -13,6 +13,7 @@ import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; import { TextModel } from 'vs/editor/common/model/textModel'; import { ISnippetsService } from 'vs/workbench/parts/snippets/electron-browser/snippets.contribution'; import { Snippet } from 'vs/workbench/parts/snippets/electron-browser/snippetsFile'; +import { SuggestContext, SuggestTriggerKind } from 'vs/editor/common/modes'; class SimpleSnippetService implements ISnippetsService { _serviceBrand: any; @@ -40,6 +41,7 @@ suite('SnippetsService', function () { let modeService: ModeServiceImpl; let snippetService: ISnippetsService; + let suggestContext: SuggestContext = { triggerKind: SuggestTriggerKind.Invoke }; setup(function () { modeService = new ModeServiceImpl(); @@ -66,7 +68,7 @@ suite('SnippetsService', function () { const provider = new SnippetSuggestProvider(modeService, snippetService); const model = TextModel.createFromString('', undefined, modeService.getLanguageIdentifier('fooLang')); - return provider.provideCompletionItems(model, new Position(1, 1)).then(result => { + return provider.provideCompletionItems(model, new Position(1, 1), suggestContext).then(result => { assert.equal(result.incomplete, undefined); assert.equal(result.suggestions.length, 2); }); @@ -77,7 +79,7 @@ suite('SnippetsService', function () { const provider = new SnippetSuggestProvider(modeService, snippetService); const model = TextModel.createFromString('bar', undefined, modeService.getLanguageIdentifier('fooLang')); - return provider.provideCompletionItems(model, new Position(1, 4)).then(result => { + return provider.provideCompletionItems(model, new Position(1, 4), suggestContext).then(result => { assert.equal(result.incomplete, undefined); assert.equal(result.suggestions.length, 1); assert.equal(result.suggestions[0].label, 'bar'); @@ -98,18 +100,18 @@ suite('SnippetsService', function () { const provider = new SnippetSuggestProvider(modeService, snippetService); let model = TextModel.createFromString('\t { + return provider.provideCompletionItems(model, new Position(1, 7), suggestContext).then(result => { assert.equal(result.suggestions.length, 1); model.dispose(); model = TextModel.createFromString('\t { assert.equal(result.suggestions.length, 1); model.dispose(); model = TextModel.createFromString('a { assert.equal(result.suggestions.length, 0); @@ -131,9 +133,9 @@ suite('SnippetsService', function () { const provider = new SnippetSuggestProvider(modeService, snippetService); let model = TextModel.createFromString('\n\t\n>/head>', undefined, modeService.getLanguageIdentifier('fooLang')); - return provider.provideCompletionItems(model, new Position(1, 1)).then(result => { + return provider.provideCompletionItems(model, new Position(1, 1), suggestContext).then(result => { assert.equal(result.suggestions.length, 1); - return provider.provideCompletionItems(model, new Position(2, 2)); + return provider.provideCompletionItems(model, new Position(2, 2), suggestContext); }).then(result => { assert.equal(result.suggestions.length, 1); }); @@ -161,7 +163,7 @@ suite('SnippetsService', function () { const provider = new SnippetSuggestProvider(modeService, snippetService); let model = TextModel.createFromString('', undefined, modeService.getLanguageIdentifier('fooLang')); - return provider.provideCompletionItems(model, new Position(1, 1)).then(result => { + return provider.provideCompletionItems(model, new Position(1, 1), suggestContext).then(result => { assert.equal(result.suggestions.length, 2); let [first, second] = result.suggestions; assert.equal(first.label, 'first'); From 3de9efd43bb93f2ecae3bba52d003189f4e632ca Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 25 Jun 2018 12:25:31 +0200 Subject: [PATCH 008/228] fix #52615 --- src/vs/workbench/browser/parts/editor/editorCommands.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts index 22dbb6d02b1..97918df94dc 100644 --- a/src/vs/workbench/browser/parts/editor/editorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts @@ -138,6 +138,7 @@ function moveActiveTab(args: ActiveEditorMoveArguments, control: IEditor, access function moveActiveEditorToGroup(args: ActiveEditorMoveArguments, control: IEditor, accessor: ServicesAccessor): void { const editorGroupService = accessor.get(IEditorGroupsService); + const configurationService = accessor.get(IConfigurationService); const sourceGroup = control.group; let targetGroup: IEditorGroup; @@ -178,6 +179,9 @@ function moveActiveEditorToGroup(args: ActiveEditorMoveArguments, control: IEdit break; case 'next': targetGroup = editorGroupService.findGroup({ location: GroupLocation.NEXT }, sourceGroup); + if (!targetGroup) { + targetGroup = editorGroupService.addGroup(sourceGroup, preferredSideBySideGroupDirection(configurationService)); + } break; case 'center': targetGroup = editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE)[(editorGroupService.count / 2) - 1]; From a8f5bf8950318d9c014570926c93ce0e820f3a27 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 22 Jun 2018 17:11:00 +0200 Subject: [PATCH 009/228] Adopt recommendations - Get recommendation source along with recommendations - Add recommendation source to extensions - Adopt recommendations for muliple management --- .../common/extensionManagement.ts | 21 +- .../parts/extensions/common/extensions.ts | 3 +- .../electron-browser/extensionTipsService.ts | 222 +++++++++++------- .../electron-browser/extensionsActions.ts | 205 ++++++++-------- .../electron-browser/extensionsUtils.ts | 4 +- .../electron-browser/extensionsViews.ts | 162 +++++++++---- .../node/extensionsWorkbenchService.ts | 71 +++--- .../extensionsTipsService.test.ts | 14 +- 8 files changed, 407 insertions(+), 295 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 47fb87b160d..fe5cf912735 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -12,6 +12,7 @@ import { IPager } from 'vs/base/common/paging'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ILocalization } from 'vs/platform/localizations/common/localizations'; import URI from 'vs/base/common/uri'; +import { IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/workspace'; export const EXTENSION_IDENTIFIER_PATTERN = '^([a-z0-9A-Z][a-z0-9\-A-Z]*)\\.([a-z0-9A-Z][a-z0-9\-A-Z]*)$'; export const EXTENSION_IDENTIFIER_REGEX = new RegExp(EXTENSION_IDENTIFIER_PATTERN); @@ -392,15 +393,27 @@ export type RecommendationChangeNotification = { isRecommended: boolean }; +export type DynamicRecommendation = 'dynamic'; +export type ExecutableRecommendation = 'executable'; +export type CachedRecommendation = 'cached'; +export type ApplicationRecommendation = 'application'; +export type ExtensionRecommendationSource = IWorkspace | IWorkspaceFolder | URI | DynamicRecommendation | ExecutableRecommendation | CachedRecommendation | ApplicationRecommendation; + +export interface IExtensionRecommendation { + extensionId: string; + sources: ExtensionRecommendationSource[]; +} + export const IExtensionTipsService = createDecorator('extensionTipsService'); export interface IExtensionTipsService { _serviceBrand: any; getAllRecommendationsWithReason(): { [id: string]: { reasonId: ExtensionRecommendationReason, reasonText: string }; }; - getFileBasedRecommendations(): string[]; - getOtherRecommendations(): TPromise; - getWorkspaceRecommendations(): TPromise; - getKeymapRecommendations(): string[]; + getFileBasedRecommendations(): IExtensionRecommendation[]; + getOtherRecommendations(): TPromise; + getWorkspaceRecommendations(): TPromise; + getKeymapRecommendations(): IExtensionRecommendation[]; + getAllRecommendations(): TPromise; getKeywordsForExtension(extension: string): string[]; getRecommendationsForExtension(extension: string): string[]; getAllIgnoredRecommendations(): IIgnoredRecommendations; diff --git a/src/vs/workbench/parts/extensions/common/extensions.ts b/src/vs/workbench/parts/extensions/common/extensions.ts index 0d834a401bd..64b3a90a7b2 100644 --- a/src/vs/workbench/parts/extensions/common/extensions.ts +++ b/src/vs/workbench/parts/extensions/common/extensions.ts @@ -8,7 +8,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { Event } from 'vs/base/common/event'; import { TPromise } from 'vs/base/common/winjs.base'; import { IPager } from 'vs/base/common/paging'; -import { IQueryOptions, IExtensionManifest, LocalExtensionType, EnablementState, ILocalExtension, IGalleryExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IQueryOptions, IExtensionManifest, LocalExtensionType, EnablementState, ILocalExtension, IGalleryExtension, ExtensionRecommendationSource } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IViewContainersRegistry, ViewContainer, Extensions as ViewContainerExtensions } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -58,6 +58,7 @@ export interface IExtension { locals?: ILocalExtension[]; gallery?: IGalleryExtension; isMalicious: boolean; + recommendationSources: ExtensionRecommendationSource[]; } export interface IExtensionDependencies { diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts index 128a8ee02a6..6d9c8399e3d 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts @@ -10,10 +10,7 @@ import { forEach } from 'vs/base/common/collections'; import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; import { match } from 'vs/base/common/glob'; import * as json from 'vs/base/common/json'; -import { - IExtensionManagementService, IExtensionGalleryService, IExtensionTipsService, ExtensionRecommendationReason, LocalExtensionType, EXTENSION_IDENTIFIER_PATTERN, - IIgnoredRecommendations, IExtensionsConfigContent, RecommendationChangeNotification, InstallOperation -} from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionManagementService, IExtensionGalleryService, IExtensionTipsService, ExtensionRecommendationReason, LocalExtensionType, EXTENSION_IDENTIFIER_PATTERN, IIgnoredRecommendations, IExtensionsConfigContent, RecommendationChangeNotification, IExtensionRecommendation, ExtensionRecommendationSource, IExtensionManagementServerService, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ITextModel } from 'vs/editor/common/model'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; @@ -22,14 +19,13 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ShowRecommendedExtensionsAction, InstallWorkspaceRecommendedExtensionsAction, InstallRecommendedExtensionAction } from 'vs/workbench/parts/extensions/electron-browser/extensionsActions'; import Severity from 'vs/base/common/severity'; import { IWorkspaceContextService, IWorkspaceFolder, IWorkspace, IWorkspaceFoldersChangeEvent, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { Schemas } from 'vs/base/common/network'; import { IFileService } from 'vs/platform/files/common/files'; import { IExtensionsConfiguration, ConfigurationKey, ShowRecommendationsOnlyOnDemandKey, IExtensionsViewlet } from 'vs/workbench/parts/extensions/common/extensions'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import * as pfs from 'vs/base/node/pfs'; import * as os from 'os'; -import { flatten, distinct, shuffle } from 'vs/base/common/arrays'; +import { flatten, distinct, shuffle, coalesce } from 'vs/base/common/arrays'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { guessMimeTypes, MIME_UNKNOWN } from 'vs/base/common/mime'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -41,6 +37,8 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { Emitter, Event } from 'vs/base/common/event'; import { assign } from 'vs/base/common/objects'; +import URI from 'vs/base/common/uri'; +import { areSameExtensions, getGalleryExtensionIdFromLocal } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; const empty: { [key: string]: any; } = Object.create(null); const milliSecondsInADay = 1000 * 60 * 60 * 24; @@ -66,10 +64,10 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe _serviceBrand: any; - private _fileBasedRecommendations: { [id: string]: number; } = Object.create(null); + private _fileBasedRecommendations: { [id: string]: { recommendedTime: number, sources: ExtensionRecommendationSource[] }; } = Object.create(null); private _exeBasedRecommendations: { [id: string]: string; } = Object.create(null); private _availableRecommendations: { [pattern: string]: string[] } = Object.create(null); - private _allWorkspaceRecommendedExtensions: string[] = []; + private _allWorkspaceRecommendedExtensions: IExtensionRecommendation[] = []; private _dynamicWorkspaceRecommendations: string[] = []; private _allIgnoredRecommendations: string[] = []; private _globallyIgnoredRecommendations: string[] = []; @@ -97,7 +95,8 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe @IRequestService private requestService: IRequestService, @IViewletService private viewletService: IViewletService, @INotificationService private notificationService: INotificationService, - @IExtensionManagementService private extensionManagementService: IExtensionManagementService + @IExtensionManagementService private extensionManagementService: IExtensionManagementService, + @IExtensionManagementServerService private extensionManagementServiceService: IExtensionManagementServerService ) { super(); @@ -199,7 +198,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe reasonText: localize('fileBasedRecommendation', "This extension is recommended based on the files you recently opened.") }); - this._allWorkspaceRecommendedExtensions.forEach(x => output[x.toLowerCase()] = { + this._allWorkspaceRecommendedExtensions.forEach(({ extensionId }) => output[extensionId.toLowerCase()] = { reasonId: ExtensionRecommendationReason.Workspace, reasonText: localize('workspaceRecommendation', "This extension is recommended by users of the current workspace.") }); @@ -207,28 +206,67 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe return output; } - getWorkspaceRecommendations(): TPromise { + getWorkspaceRecommendations(): TPromise { if (!this.isEnabled()) { return TPromise.as([]); } - - return this.fetchCombinedExtensionRecommendationConfig() - .then(content => { - this._workspaceIgnoredRecommendations = content.unwantedRecommendations; - this._allIgnoredRecommendations = distinct([...this._globallyIgnoredRecommendations, ...this._workspaceIgnoredRecommendations]); - this._allWorkspaceRecommendedExtensions = content.recommendations; - this.refilterAllRecommendations(); - return this._allWorkspaceRecommendedExtensions; - }); + return this.fetchWorkspaceRecommendations().then(() => this._allWorkspaceRecommendedExtensions); } - private fetchCombinedExtensionRecommendationConfig(): TPromise { - const mergeExtensionRecommendationConfigs: (configs: IExtensionsConfigContent[]) => IExtensionsConfigContent = configs => ({ - recommendations: distinct(flatten(configs.map(config => config && config.recommendations || []))), - unwantedRecommendations: distinct(flatten(configs.map(config => config && config.unwantedRecommendations || []))) - }); + private fetchWorkspaceRecommendations(): TPromise { + this._workspaceIgnoredRecommendations = []; + this._allWorkspaceRecommendedExtensions = []; + if (!this.isEnabled) { return TPromise.as(null); } + + return this.fetchExtensionRecommendationContents() + .then(result => this.validateExtensions(result.map(({ contents }) => contents)) + .then(({ invalidExtensions, message }) => { + + if (invalidExtensions.length > 0 && this.notificationService) { + this.notificationService.warn(`The below ${invalidExtensions.length} extension(s) in workspace recommendations have issues:\n${message}`); + } + + const seenUnWantedRecommendations: { [id: string]: boolean } = {}; + + for (const contentsBySource of result) { + if (contentsBySource.contents.unwantedRecommendations) { + for (const r of contentsBySource.contents.unwantedRecommendations) { + const unwantedRecommendation = r.toLowerCase(); + if (!seenUnWantedRecommendations[unwantedRecommendation] && invalidExtensions.indexOf(unwantedRecommendation) === -1) { + this._workspaceIgnoredRecommendations.push(unwantedRecommendation); + seenUnWantedRecommendations[unwantedRecommendation] = true; + } + } + } + + if (contentsBySource.contents.recommendations) { + for (const r of contentsBySource.contents.recommendations) { + const extensionId = r.toLowerCase(); + if (invalidExtensions.indexOf(extensionId) === -1) { + let recommendation = this._allWorkspaceRecommendedExtensions.filter(r => r.extensionId === extensionId)[0]; + if (!recommendation) { + recommendation = { extensionId, sources: [] }; + this._allWorkspaceRecommendedExtensions.push(recommendation); + } + if (recommendation.sources.indexOf(contentsBySource.source) === -1) { + recommendation.sources.push(contentsBySource.source); + } + } + } + } + } + + this._allIgnoredRecommendations = distinct([...this._globallyIgnoredRecommendations, ...this._workspaceIgnoredRecommendations]); + this.refilterAllRecommendations(); + + })); + } + + private fetchExtensionRecommendationContents(): TPromise<{ contents: IExtensionsConfigContent, source: ExtensionRecommendationSource }[]> { const workspace = this.contextService.getWorkspace(); - return TPromise.join([this.resolveWorkspaceExtensionConfig(workspace), ...workspace.folders.map(workspaceFolder => this.resolveWorkspaceFolderExtensionConfig(workspaceFolder))]) - .then(contents => this.processConfigContent(mergeExtensionRecommendationConfigs(contents))); + return TPromise.join<{ contents: IExtensionsConfigContent, source: ExtensionRecommendationSource }>([ + this.resolveWorkspaceExtensionConfig(workspace).then(contents => contents ? { contents, source: workspace } : null), + ...workspace.folders.map(workspaceFolder => this.resolveWorkspaceFolderExtensionConfig(workspaceFolder).then(contents => contents ? { contents, source: workspaceFolder } : null)) + ]).then(contents => coalesce(contents)); } private resolveWorkspaceExtensionConfig(workspace: IWorkspace): TPromise { @@ -248,47 +286,36 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe .then(content => json.parse(content.value), err => null); } - private processConfigContent(extensionsContent: IExtensionsConfigContent): TPromise { - if (!extensionsContent) { - return TPromise.as({ recommendations: [], unwantedRecommendations: [] }); - } + private validateExtensions(contents: IExtensionsConfigContent[]): TPromise<{ invalidExtensions: string[], message: string }> { + const extensionsContent: IExtensionsConfigContent = { + recommendations: distinct(flatten(contents.map(content => content.recommendations || []))), + unwantedRecommendations: distinct(flatten(contents.map(content => content.unwantedRecommendations || []))) + }; const regEx = new RegExp(EXTENSION_IDENTIFIER_PATTERN); - let countBadRecommendations = 0; - let badRecommendationsString = ''; - let errorsNotification = () => { - if (countBadRecommendations > 0 && this.notificationService) { - this.notificationService.warn( - 'The below ' + - countBadRecommendations + - ' extension(s) in workspace recommendations have issues:\n' + - badRecommendationsString - ); - } - }; + const invalidExtensions = []; + let message = ''; - let regexFilter = (ids: string[]) => { + const regexFilter = (ids: string[]) => { return ids.filter((element, position) => { if (ids.indexOf(element) !== position) { // This is a duplicate entry, it doesn't hurt anybody // but it shouldn't be sent in the gallery query return false; } else if (!regEx.test(element)) { - countBadRecommendations++; - badRecommendationsString += `${element} (bad format) Expected: .\n`; + invalidExtensions.push(element.toLowerCase()); + message += `${element} (bad format) Expected: .\n`; return false; } return true; }); }; - let filteredWanted = regexFilter(extensionsContent.recommendations || []).map(x => x.toLowerCase()); - let filteredUnwanted = regexFilter(extensionsContent.unwantedRecommendations || []).map(x => x.toLowerCase()); + const filteredWanted = regexFilter(extensionsContent.recommendations || []).map(x => x.toLowerCase()); if (!filteredWanted.length) { - errorsNotification(); - return TPromise.as({ recommendations: filteredWanted, unwantedRecommendations: filteredUnwanted }); + return TPromise.as({ invalidExtensions, message }); } return this._galleryService.query({ names: filteredWanted }).then(pager => { @@ -300,19 +327,18 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe if (validRecommendations.length !== filteredWanted.length) { filteredWanted.forEach(element => { if (validRecommendations.indexOf(element.toLowerCase()) === -1) { - countBadRecommendations++; - badRecommendationsString += `${element} (not found in marketplace)\n`; + invalidExtensions.push(element.toLowerCase()); + message += `${element} (not found in marketplace)\n`; } }); } - errorsNotification(); - return { recommendations: validRecommendations, unwantedRecommendations: filteredUnwanted }; + return TPromise.as({ invalidExtensions, message }); }); } private refilterAllRecommendations() { - this._allWorkspaceRecommendedExtensions = this._allWorkspaceRecommendedExtensions.filter((id) => this.isExtensionAllowedToBeRecommended(id)); + this._allWorkspaceRecommendedExtensions = this._allWorkspaceRecommendedExtensions.filter(({ extensionId }) => this.isExtensionAllowedToBeRecommended(extensionId)); this._dynamicWorkspaceRecommendations = this._dynamicWorkspaceRecommendations.filter((id) => this.isExtensionAllowedToBeRecommended(id)); this._allIgnoredRecommendations.forEach(x => { @@ -343,20 +369,21 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe private onWorkspaceFoldersChanged(event: IWorkspaceFoldersChangeEvent): void { if (event.added.length) { const oldWorkspaceRecommended = this._allWorkspaceRecommendedExtensions; - this.getWorkspaceRecommendations().then(result => { - // Suggest only if at least one of the newly added recommendations was not suggested before - if (result.some(e => oldWorkspaceRecommended.indexOf(e) === -1)) { - this._suggestWorkspaceRecommendations(); - } - }); + this.getWorkspaceRecommendations() + .then(currentWorkspaceRecommended => { + // Suggest only if at least one of the newly added recommendations was not suggested before + if (currentWorkspaceRecommended.some(current => oldWorkspaceRecommended.every(old => current.extensionId !== old.extensionId))) { + this._suggestWorkspaceRecommendations(); + } + }); } this._dynamicWorkspaceRecommendations = []; } - getFileBasedRecommendations(): string[] { - const fileBased = Object.keys(this._fileBasedRecommendations) + getFileBasedRecommendations(): IExtensionRecommendation[] { + return Object.keys(this._fileBasedRecommendations) .sort((a, b) => { - if (this._fileBasedRecommendations[a] === this._fileBasedRecommendations[b]) { + if (this._fileBasedRecommendations[a].recommendedTime === this._fileBasedRecommendations[b].recommendedTime) { if (!product.extensionImportantTips || caseInsensitiveGet(product.extensionImportantTips, a)) { return -1; } @@ -364,21 +391,39 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe return 1; } } - return this._fileBasedRecommendations[a] > this._fileBasedRecommendations[b] ? -1 : 1; - }); - return fileBased; + return this._fileBasedRecommendations[a].recommendedTime > this._fileBasedRecommendations[b].recommendedTime ? -1 : 1; + }) + .map(extensionId => ({ extensionId, sources: this._fileBasedRecommendations[extensionId].sources })); } - getOtherRecommendations(): TPromise { + getOtherRecommendations(): TPromise { return this.fetchProactiveRecommendations().then(() => { const others = distinct([...Object.keys(this._exeBasedRecommendations), ...this._dynamicWorkspaceRecommendations]); shuffle(others); - return others; + return others.map(extensionId => { + const sources: ExtensionRecommendationSource[] = []; + if (this._exeBasedRecommendations[extensionId]) { + sources.push('executable'); + } + if (this._dynamicWorkspaceRecommendations[extensionId]) { + sources.push('dynamic'); + } + return ({ extensionId, sources }); + }); }); } - getKeymapRecommendations(): string[] { - return product.keymapExtensionTips || []; + getKeymapRecommendations(): IExtensionRecommendation[] { + return (product.keymapExtensionTips || []).map(extensionId => ({ extensionId, sources: ['application'] })); + } + + getAllRecommendations(): TPromise { + return TPromise.join([ + this.getWorkspaceRecommendations(), + TPromise.as(this.getFileBasedRecommendations()), + this.getOtherRecommendations(), + TPromise.as(this.getKeymapRecommendations()) + ]).then(result => flatten(result)); } private _suggestFileBasedRecommendations() { @@ -422,7 +467,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe if (Array.isArray(storedRecommendationsJson)) { for (let id of storedRecommendationsJson) { if (allRecommendations.indexOf(id) > -1) { - this._fileBasedRecommendations[id.toLowerCase()] = Date.now(); + this._fileBasedRecommendations[id.toLowerCase()] = { recommendedTime: Date.now(), sources: ['cached'] }; } } } else { @@ -431,7 +476,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe if (typeof entry.value === 'number') { const diff = (now - entry.value) / milliSecondsInADay; if (diff <= 7 && allRecommendations.indexOf(entry.key) > -1) { - this._fileBasedRecommendations[entry.key.toLowerCase()] = entry.value; + this._fileBasedRecommendations[entry.key.toLowerCase()] = { recommendedTime: entry.value, sources: ['cached'] }; } } }); @@ -448,11 +493,11 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe const uri = model.uri; let hasSuggestion = false; - if (!uri || uri.scheme !== Schemas.file) { + if (!uri) { return; } - let fileExtension = paths.extname(uri.fsPath); + let fileExtension = paths.extname(uri.path); if (fileExtension) { if (processedFileExtensions.indexOf(fileExtension) > -1) { return; @@ -468,19 +513,23 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe const now = Date.now(); forEach(this._availableRecommendations, entry => { let { key: pattern, value: ids } = entry; - if (match(pattern, uri.fsPath)) { + if (match(pattern, uri.path)) { for (let id of ids) { if (Object.keys(product.extensionImportantTips || []).map(x => x.toLowerCase()).indexOf(id.toLowerCase()) > -1) { recommendationsToSuggest.push(id); } - this._fileBasedRecommendations[id.toLowerCase()] = now; + const filedBasedRecommendation = this._fileBasedRecommendations[id.toLowerCase()] || { recommendedTime: now, sources: [] }; + if (!filedBasedRecommendation.sources.some(s => s instanceof URI && s.toString() === uri.toString())) { + filedBasedRecommendation.sources.push(uri); + } + this._fileBasedRecommendations[id.toLowerCase()] = filedBasedRecommendation; } } }); this.storageService.store( 'extensionsAssistant/recommendations', - JSON.stringify(this._fileBasedRecommendations), + JSON.stringify(Object.keys(this._fileBasedRecommendations).reduce((result, key) => { result[key] = this._fileBasedRecommendations[key].recommendedTime; return result; }, {})), StorageScope.GLOBAL ); @@ -492,7 +541,8 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe const importantRecommendationsIgnoreList = JSON.parse(this.storageService.get('extensionsAssistant/importantRecommendationsIgnore', StorageScope.GLOBAL, '[]')); recommendationsToSuggest = recommendationsToSuggest.filter(id => importantRecommendationsIgnoreList.indexOf(id) === -1); - const importantTipsPromise = recommendationsToSuggest.length === 0 ? TPromise.as(null) : this.extensionsService.getInstalled(LocalExtensionType.User).then(local => { + const server = this.extensionManagementServiceService.getExtensionManagementServer(model.uri); + const importantTipsPromise = recommendationsToSuggest.length === 0 ? TPromise.as(null) : server.extensionManagementService.getInstalled(LocalExtensionType.User).then(local => { recommendationsToSuggest = recommendationsToSuggest.filter(id => local.every(local => `${local.manifest.publisher}.${local.manifest.name}` !== id)); if (!recommendationsToSuggest.length) { return; @@ -521,9 +571,8 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe */ this.telemetryService.publicLog('extensionRecommendations:popup', { userReaction: 'install', extensionId: name }); - const installAction = this.instantiationService.createInstance(InstallRecommendedExtensionAction, id); - installAction.run(); - installAction.dispose(); + const installAction = this.instantiationService.createInstance(InstallRecommendedExtensionAction, id, server); + installAction.run().then(() => installAction.dispose()); } }, { label: localize('showRecommendations', "Show Recommendations"), @@ -653,17 +702,16 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe } private _suggestWorkspaceRecommendations(): void { - const allRecommendations = this._allWorkspaceRecommendedExtensions; const storageKey = 'extensionsAssistant/workspaceRecommendationsIgnore'; const config = this.configurationService.getValue(ConfigurationKey); - if (!allRecommendations.length || config.ignoreRecommendations || config.showRecommendationsOnlyOnDemand || this.storageService.getBoolean(storageKey, StorageScope.WORKSPACE, false)) { + if (!this._allWorkspaceRecommendedExtensions.length || config.ignoreRecommendations || config.showRecommendationsOnlyOnDemand || this.storageService.getBoolean(storageKey, StorageScope.WORKSPACE, false)) { return; } return this.extensionsService.getInstalled(LocalExtensionType.User).done(local => { - const recommendations = allRecommendations - .filter(id => local.every(local => `${local.manifest.publisher.toLowerCase()}.${local.manifest.name.toLowerCase()}` !== id)); + const recommendations = this._allWorkspaceRecommendedExtensions + .filter(({ extensionId }) => local.every(local => !areSameExtensions({ id: extensionId }, { id: getGalleryExtensionIdFromLocal(local) }))); if (!recommendations.length) { return TPromise.as(void 0); @@ -683,7 +731,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe */ this.telemetryService.publicLog('extensionWorkspaceRecommendations:popup', { userReaction: 'install' }); - const installAllAction = this.instantiationService.createInstance(InstallWorkspaceRecommendedExtensionsAction, InstallWorkspaceRecommendedExtensionsAction.ID, localize('installAll', "Install All")); + const installAllAction = this.instantiationService.createInstance(InstallWorkspaceRecommendedExtensionsAction, InstallWorkspaceRecommendedExtensionsAction.ID, localize('installAll', "Install All"), recommendations); installAllAction.run(); installAllAction.dispose(); diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts index 1fc7c52ca08..dc54ae61d80 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts @@ -18,7 +18,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewlet, AutoUpdateConfigurationKey } from 'vs/workbench/parts/extensions/common/extensions'; import { ExtensionsConfigurationInitialContent } from 'vs/workbench/parts/extensions/common/extensionsFileTemplate'; -import { LocalExtensionType, IExtensionEnablementService, IExtensionTipsService, EnablementState, ExtensionsLabel, IExtensionManagementServer, IExtensionManagementServerService, IGalleryExtension, ILocalExtension, IExtensionsConfigContent } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { LocalExtensionType, IExtensionEnablementService, IExtensionTipsService, EnablementState, ExtensionsLabel, IExtensionManagementServer, IExtensionManagementServerService, IExtensionRecommendation, ExtensionRecommendationSource, IExtensionGalleryService, IGalleryExtension, ILocalExtension, IExtensionsConfigContent } from 'vs/platform/extensionManagement/common/extensionManagement'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ToggleViewletAction } from 'vs/workbench/browser/viewlet'; @@ -73,6 +73,21 @@ const promptDownloadManually = (extension: IGalleryExtension, message: string, i }]); }; +const getExtensionManagementServerForRecommendationSource = (source: ExtensionRecommendationSource, extensionManagementServerService: IExtensionManagementServerService, contextService: IWorkspaceContextService): IExtensionManagementServer => { + if (source instanceof URI) { + return extensionManagementServerService.getExtensionManagementServer(source); + } + if (source === contextService.getWorkspace()) { + return extensionManagementServerService.getDefaultExtensionManagementServer(); + } + for (const workspaceFolder of contextService.getWorkspace().folders) { + if (source === workspaceFolder) { + return extensionManagementServerService.getExtensionManagementServer(workspaceFolder.uri); + } + } + return extensionManagementServerService.getDefaultExtensionManagementServer(); +}; + export interface IExtensionAction extends IAction { extension: IExtension; } @@ -446,7 +461,8 @@ export class MultiServerInstallAction extends Action { constructor( @IExtensionManagementServerService private extensionManagementServerService: IExtensionManagementServerService, @IInstantiationService private instantiationService: IInstantiationService, - @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService + @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, + @IWorkspaceContextService private contextService: IWorkspaceContextService ) { super(MultiServerInstallAction.ID, MultiServerInstallAction.InstallLabel, MultiServerInstallAction.Class, false); this._installActions = this.extensionManagementServerService.extensionManagementServers.map(server => this.instantiationService.createInstance(InstallGalleryExtensionAction, `extensions.install.${server.location.authority}`, localize('installInServer', "{0}", server.location.authority), server)); @@ -464,7 +480,21 @@ export class MultiServerInstallAction extends Action { return; } - this.enabled = this.extensionsWorkbenchService.canInstall(this.extension) && this.extension.state === ExtensionState.Uninstalled; + let isExtensionNotInstalledInRecommendedServer: boolean = false; + this._installActions.forEach((installAction, index) => { + const server = this.extensionManagementServerService.extensionManagementServers[index]; + installAction.extension = this.extension.gallery; + installAction.label = localize('installInServer', "{0}", server.location.authority); + installAction.enabled = !this.extension.locals.some(local => this.extensionManagementServerService.getExtensionManagementServer(local.location) === server); + if (this.extension.recommendationSources && this.extension.recommendationSources.length) { + if (this.extension.recommendationSources.some(recommendationSource => getExtensionManagementServerForRecommendationSource(recommendationSource, this.extensionManagementServerService, this.contextService) === server)) { + installAction.label = localize('installInRecommendedServer', "{0} (Recommended)", server.location.authority); + isExtensionNotInstalledInRecommendedServer = isExtensionNotInstalledInRecommendedServer || installAction.enabled; + } + } + }); + + this.enabled = this.extensionsWorkbenchService.canInstall(this.extension) && (isExtensionNotInstalledInRecommendedServer || this.extension.locals.length === 0); if (this.extension.state === ExtensionState.Installing) { this.label = MultiServerInstallAction.InstallingLabel; @@ -475,10 +505,6 @@ export class MultiServerInstallAction extends Action { this.class = MultiServerInstallAction.Class; this.tooltip = MultiServerInstallAction.InstallLabel; } - - for (const installAction of this._installActions) { - installAction.extension = this.extension ? this.extension.gallery : null; - } } public run(): TPromise { @@ -522,14 +548,6 @@ export class MultiServerUpdateAction extends Action { } private update(): void { - if (this.extension && this.extension.type === LocalExtensionType.User) { - const canInstall = this.extensionsWorkbenchService.canInstall(this.extension); - const isInstalled = this.extension.state === ExtensionState.Installed; - this.enabled = canInstall && isInstalled; - } else { - this.enabled = false; - } - this._updateActions.forEach((updateAction, index) => { updateAction.extension = null; if (this.extension && this.extension.locals && this.extension.gallery) { @@ -538,6 +556,7 @@ export class MultiServerUpdateAction extends Action { updateAction.extension = { local, gallery: this.extension.gallery }; } }); + this.enabled = this._updateActions.some(action => action.enabled); } public run(): TPromise { @@ -1382,82 +1401,69 @@ export class InstallWorkspaceRecommendedExtensionsAction extends Action { private disposables: IDisposable[] = []; + private _recommendations: IExtensionRecommendation[] = []; + get recommendations(): IExtensionRecommendation[] { return this._recommendations; } + set recommendations(recommendations: IExtensionRecommendation[]) { this._recommendations = recommendations; this.enabled = this._recommendations.length > 0; } + constructor( id: string = InstallWorkspaceRecommendedExtensionsAction.ID, label: string = InstallWorkspaceRecommendedExtensionsAction.LABEL, + recommendations: IExtensionRecommendation[], @IWorkspaceContextService private contextService: IWorkspaceContextService, @IViewletService private viewletService: IViewletService, - @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, - @IExtensionTipsService private extensionTipsService: IExtensionTipsService, @INotificationService private notificationService: INotificationService, @IInstantiationService private instantiationService: IInstantiationService, - @IOpenerService private openerService: IOpenerService + @IOpenerService private openerService: IOpenerService, + @IExtensionManagementServerService private extensionManagementServerService: IExtensionManagementServerService, + @IExtensionGalleryService private extensionGalleryService: IExtensionGalleryService ) { super(id, label, 'extension-action'); - this.extensionsWorkbenchService.onChange(() => this.update(), this, this.disposables); - this.contextService.onDidChangeWorkbenchState(() => this.update(), this, this.disposables); - } - - private update(): void { - this.enabled = this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY; - if (this.enabled) { - this.extensionTipsService.getWorkspaceRecommendations().then(names => { - const installed = this.extensionsWorkbenchService.local.map(x => x.id.toLowerCase()); - this.enabled = names.some(x => installed.indexOf(x.toLowerCase()) === -1); - }); - } + this.recommendations = recommendations; } run(): TPromise { - return this.extensionTipsService.getWorkspaceRecommendations().then(names => { - const installed = this.extensionsWorkbenchService.local.map(x => x.id.toLowerCase()); - const toInstall = names.filter(x => installed.indexOf(x.toLowerCase()) === -1); + return this.viewletService.openViewlet(VIEWLET_ID, true) + .then(viewlet => viewlet as IExtensionsViewlet) + .then(viewlet => { + viewlet.search('@recommended '); + viewlet.focus(); + if (this.recommendations.length === 0) { + this.notificationService.info(localize('extensionInstalled', "The recommended extension has already been installed")); + return TPromise.as(null); + } - return this.viewletService.openViewlet(VIEWLET_ID, true) - .then(viewlet => viewlet as IExtensionsViewlet) - .then(viewlet => { - if (!toInstall.length) { - this.enabled = false; - this.notificationService.info(localize('allExtensionsInstalled', "All extensions recommended for this workspace have already been installed")); - viewlet.focus(); - return TPromise.as(null); + const names = this.recommendations.map(({ extensionId }) => extensionId); + return this.extensionGalleryService.query({ names, source: 'install-all-workspace-recommendations' }).then(pager => { + let installPromises = []; + let model = new PagedModel(pager); + for (let i = 0; i < pager.total; i++) { + installPromises.push(model.resolve(i).then(e => { + return this.install(e); + })); } - - viewlet.search('@recommended '); - viewlet.focus(); - - return this.extensionsWorkbenchService.queryGallery({ names: toInstall, source: 'install-all-workspace-recommendations' }).then(pager => { - let installPromises = []; - let model = new PagedModel(pager); - let extensionsWithDependencies = []; - for (let i = 0; i < pager.total; i++) { - installPromises.push(model.resolve(i).then(e => { - if (e.dependencies && e.dependencies.length > 0) { - extensionsWithDependencies.push(e); - return TPromise.as(null); - } else { - return this.install(e); - } - })); - } - return TPromise.join(installPromises).then(() => { - return TPromise.join(extensionsWithDependencies.map(e => this.install(e))); - }); - }); + return TPromise.join(installPromises); }); - }); + }); } - private install(extension: IExtension): TPromise { - return this.extensionsWorkbenchService.install(extension).then(null, err => { - if (!extension.gallery) { - return this.notificationService.error(err); + private install(extension: IGalleryExtension): TPromise { + const servers: IExtensionManagementServer[] = []; + const recommendation = this.recommendations.filter(r => areSameExtensions({ id: r.extensionId }, extension.identifier))[0]; + if (recommendation) { + for (const source of recommendation.sources || []) { + const server = getExtensionManagementServerForRecommendationSource(source, this.extensionManagementServerService, this.contextService); + if (servers.indexOf(server) === -1) { + servers.push(server); + } } - + } + if (!servers.length) { + servers.push(this.extensionManagementServerService.getDefaultExtensionManagementServer()); + } + return TPromise.join(servers.map(server => server.extensionManagementService.installFromGallery(extension).then(null, err => { console.error(err); - - promptDownloadManually(extension.gallery, localize('failedToInstall', "Failed to install \'{0}\'.", extension.id), this.instantiationService, this.notificationService, this.openerService); - }); + promptDownloadManually(extension, localize('failedToInstall', "Failed to install \'{0}\'.", extension.identifier.id), this.instantiationService, this.notificationService, this.openerService); + }))).then(() => null); } dispose(): void { @@ -1466,67 +1472,40 @@ export class InstallWorkspaceRecommendedExtensionsAction extends Action { } } -export class InstallRecommendedExtensionAction extends Action { +export class InstallRecommendedExtensionAction extends InstallGalleryExtensionAction { static readonly ID = 'workbench.extensions.action.installRecommendedExtension'; static LABEL = localize('installRecommendedExtension', "Install Recommended Extension"); private extensionId: string; - private disposables: IDisposable[] = []; constructor( - extensionId: string, + extensionId: string, server: IExtensionManagementServer, @IViewletService private viewletService: IViewletService, - @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, - @INotificationService private notificationService: INotificationService, - @IInstantiationService private instantiationService: IInstantiationService, - @IOpenerService private openerService: IOpenerService + @INotificationService notificationService: INotificationService, + @IInstantiationService instantiationService: IInstantiationService, + @IOpenerService openerService: IOpenerService, + @IExtensionGalleryService private extensionGalleryService: IExtensionGalleryService ) { - super(InstallRecommendedExtensionAction.ID, InstallRecommendedExtensionAction.LABEL, null); + super(InstallRecommendedExtensionAction.ID, InstallRecommendedExtensionAction.LABEL, server, notificationService, instantiationService, openerService); this.extensionId = extensionId; - this.extensionsWorkbenchService.onChange(() => this.update(), this, this.disposables); - } - - private update(): void { - this.enabled = !this.extensionsWorkbenchService.local.some(x => x.id.toLowerCase() === this.extensionId.toLowerCase()); } run(): TPromise { return this.viewletService.openViewlet(VIEWLET_ID, true) .then(viewlet => viewlet as IExtensionsViewlet) .then(viewlet => { - if (this.extensionsWorkbenchService.local.some(x => x.id.toLowerCase() === this.extensionId.toLowerCase())) { - this.enabled = false; - this.notificationService.info(localize('extensionInstalled', "The recommended extension has already been installed")); - viewlet.focus(); - return TPromise.as(null); - } - viewlet.search('@recommended '); viewlet.focus(); - - return this.extensionsWorkbenchService.queryGallery({ names: [this.extensionId], source: 'install-recommendation' }).then(pager => { - return (pager && pager.firstPage && pager.firstPage.length) ? this.install(pager.firstPage[0]) : TPromise.as(null); - }); + return this.extensionGalleryService.query({ names: [this.extensionId], source: 'install-recommendation', pageSize: 1 }) + .then(pager => { + if (pager && pager.firstPage && pager.firstPage.length) { + this.extension = pager.firstPage[0]; + } + return super.run(); + }); }); } - - private install(extension: IExtension): TPromise { - return this.extensionsWorkbenchService.install(extension).then(null, err => { - if (!extension.gallery) { - return this.notificationService.error(err); - } - - console.error(err); - - promptDownloadManually(extension.gallery, localize('failedToInstall', "Failed to install \'{0}\'.", extension.id), this.instantiationService, this.notificationService, this.openerService); - }); - } - - dispose(): void { - this.disposables = dispose(this.disposables); - super.dispose(); - } } export class IgnoreExtensionRecommendationAction extends Action { diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsUtils.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsUtils.ts index 104644f1c7f..edb456d35f0 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsUtils.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsUtils.ts @@ -16,7 +16,7 @@ import { IExtensionManagementService, ILocalExtension, IExtensionEnablementServi import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { areSameExtensions, adoptToGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { areSameExtensions, adoptToGalleryExtensionId, getGalleryExtensionIdFromLocal } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { getIdAndVersionFromLocalExtensionId } from 'vs/platform/extensionManagement/node/extensionManagementUtil'; import { Severity, INotificationService } from 'vs/platform/notification/common/notification'; @@ -136,7 +136,7 @@ export function getInstalledExtensions(accessor: ServicesAccessor): TPromise areSameExtensions({ id: extensionId }, { id: getGalleryExtensionIdFromLocal(extension.local) })); } function stripVersion(id: string): string { diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts index d02b477fac7..b22cdb46bd2 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts @@ -12,7 +12,7 @@ import { assign } from 'vs/base/common/objects'; import { chain } from 'vs/base/common/event'; import { isPromiseCanceledError, create as createError } from 'vs/base/common/errors'; import { PagedModel, IPagedModel, IPager } from 'vs/base/common/paging'; -import { SortBy, SortOrder, IQueryOptions, LocalExtensionType, IExtensionTipsService, EnablementState } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { SortBy, SortOrder, IQueryOptions, LocalExtensionType, IExtensionTipsService, EnablementState, IExtensionRecommendation, IExtensionManagementServerService, ExtensionRecommendationSource, IExtensionManagementServer } from 'vs/platform/extensionManagement/common/extensionManagement'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; @@ -36,6 +36,9 @@ import { WorkbenchPagedList } from 'vs/platform/list/browser/listService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { distinct } from 'vs/base/common/arrays'; +import URI from 'vs/base/common/uri'; export class ExtensionsListView extends ViewletPanel { @@ -53,12 +56,14 @@ export class ExtensionsListView extends ViewletPanel { @IInstantiationService protected instantiationService: IInstantiationService, @IThemeService private themeService: IThemeService, @IExtensionService private extensionService: IExtensionService, - @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionsWorkbenchService protected extensionsWorkbenchService: IExtensionsWorkbenchService, @IEditorService private editorService: IEditorService, @IExtensionTipsService protected tipsService: IExtensionTipsService, @IModeService private modeService: IModeService, @ITelemetryService private telemetryService: ITelemetryService, - @IConfigurationService configurationService: IConfigurationService + @IConfigurationService configurationService: IConfigurationService, + @IWorkspaceContextService protected contextService: IWorkspaceContextService, + @IExtensionManagementServerService protected extensionManagementServerService: IExtensionManagementServerService ) { super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService); } @@ -80,6 +85,7 @@ export class ExtensionsListView extends ViewletPanel { this.list = this.instantiationService.createInstance(WorkbenchPagedList, this.extensionsList, delegate, [renderer], { ariaLabel: localize('extensions', "Extensions") }) as WorkbenchPagedList; + this.disposables.push(this.list); chain(this.list.onOpen) .map(e => e.elements[0]) @@ -326,14 +332,13 @@ export class ExtensionsListView extends ViewletPanel { return this.extensionsWorkbenchService.queryLocal() .then(result => result.filter(e => e.type === LocalExtensionType.User)) .then(local => { - const installedExtensions = local.map(x => `${x.publisher}.${x.name}`); - let fileBasedRecommendations = this.tipsService.getFileBasedRecommendations(); + const fileBasedRecommendations = this.tipsService.getFileBasedRecommendations(); const othersPromise = this.tipsService.getOtherRecommendations(); const workspacePromise = this.tipsService.getWorkspaceRecommendations(); return TPromise.join([othersPromise, workspacePromise]) .then(([others, workspaceRecommendations]) => { - const names = this.getTrimmedRecommendations(installedExtensions, value, fileBasedRecommendations, others, workspaceRecommendations); + const names = this.getTrimmedRecommendations(local, value, fileBasedRecommendations, others, workspaceRecommendations); const recommendationsWithReason = this.tipsService.getAllRecommendationsWithReason(); /* __GDPR__ "extensionAllRecommendations:open" : { @@ -369,18 +374,16 @@ export class ExtensionsListView extends ViewletPanel { return this.extensionsWorkbenchService.queryLocal() .then(result => result.filter(e => e.type === LocalExtensionType.User)) .then(local => { - const installedExtensions = local.map(x => `${x.publisher}.${x.name}`); let fileBasedRecommendations = this.tipsService.getFileBasedRecommendations(); const othersPromise = this.tipsService.getOtherRecommendations(); const workspacePromise = this.tipsService.getWorkspaceRecommendations(); return TPromise.join([othersPromise, workspacePromise]) .then(([others, workspaceRecommendations]) => { - workspaceRecommendations = workspaceRecommendations.map(x => x.toLowerCase()); - fileBasedRecommendations = fileBasedRecommendations.filter(x => workspaceRecommendations.indexOf(x.toLowerCase()) === -1); - others = others.filter(x => workspaceRecommendations.indexOf(x.toLowerCase()) === -1); + fileBasedRecommendations = fileBasedRecommendations.filter(x => workspaceRecommendations.every(({ extensionId }) => x.extensionId !== extensionId)); + others = others.filter(x => x => workspaceRecommendations.every(({ extensionId }) => x.extensionId !== extensionId)); - const names = this.getTrimmedRecommendations(installedExtensions, value, fileBasedRecommendations, others, []); + const names = this.getTrimmedRecommendations(local, value, fileBasedRecommendations, others, []); const recommendationsWithReason = this.tipsService.getAllRecommendationsWithReason(); /* __GDPR__ @@ -413,39 +416,75 @@ export class ExtensionsListView extends ViewletPanel { } // Given all recommendations, trims and returns recommendations in the relevant order after filtering out installed extensions - private getTrimmedRecommendations(installedExtensions: string[], value: string, fileBasedRecommendations: string[], otherRecommendations: string[], workpsaceRecommendations: string[], ) { + private getTrimmedRecommendations(installedExtensions: IExtension[], value: string, fileBasedRecommendations: IExtensionRecommendation[], otherRecommendations: IExtensionRecommendation[], workpsaceRecommendations: IExtensionRecommendation[]): string[] { const totalCount = 8; workpsaceRecommendations = workpsaceRecommendations - .filter(name => { - return installedExtensions.indexOf(name) === -1 - && name.toLowerCase().indexOf(value) > -1; + .filter(recommendation => { + return !this.isRecommendationInstalled(recommendation, installedExtensions) + && recommendation.extensionId.toLowerCase().indexOf(value) > -1; }); - fileBasedRecommendations = fileBasedRecommendations.filter(x => { - return installedExtensions.indexOf(x) === -1 - && workpsaceRecommendations.indexOf(x) === -1 - && x.toLowerCase().indexOf(value) > -1; + fileBasedRecommendations = fileBasedRecommendations.filter(recommendation => { + return !this.isRecommendationInstalled(recommendation, installedExtensions) + && workpsaceRecommendations.every(workspaceRecommendation => workspaceRecommendation.extensionId !== recommendation.extensionId) + && recommendation.extensionId.toLowerCase().indexOf(value) > -1; }); - otherRecommendations = otherRecommendations.filter(x => { - return installedExtensions.indexOf(x) === -1 - && fileBasedRecommendations.indexOf(x) === -1 - && workpsaceRecommendations.indexOf(x) === -1 - && x.toLowerCase().indexOf(value) > -1; + otherRecommendations = otherRecommendations.filter(recommendation => { + return !this.isRecommendationInstalled(recommendation, installedExtensions) + && fileBasedRecommendations.every(fileBasedRecommendation => fileBasedRecommendation.extensionId !== recommendation.extensionId) + && workpsaceRecommendations.every(workspaceRecommendation => workspaceRecommendation.extensionId !== recommendation.extensionId) + && recommendation.extensionId.toLowerCase().indexOf(value) > -1; }); - let otherCount = Math.min(2, otherRecommendations.length); - let fileBasedCount = Math.min(fileBasedRecommendations.length, totalCount - workpsaceRecommendations.length - otherCount); - let names = workpsaceRecommendations; - names.push(...fileBasedRecommendations.splice(0, fileBasedCount)); - names.push(...otherRecommendations.splice(0, otherCount)); + const otherCount = Math.min(2, otherRecommendations.length); + const fileBasedCount = Math.min(fileBasedRecommendations.length, totalCount - workpsaceRecommendations.length - otherCount); + const recommendations = workpsaceRecommendations; + recommendations.push(...fileBasedRecommendations.splice(0, fileBasedCount)); + recommendations.push(...otherRecommendations.splice(0, otherCount)); - return names; + return distinct(recommendations.map(({ extensionId }) => extensionId)); + } + + private isRecommendationInstalled(recommendation: IExtensionRecommendation, installed: IExtension[]): boolean { + const extension = installed.filter(i => areSameExtensions({ id: i.id }, { id: recommendation.extensionId }))[0]; + if (extension && extension.locals) { + const servers: IExtensionManagementServer[] = []; + for (const local of extension.locals) { + const server = this.extensionManagementServerService.getExtensionManagementServer(local.location); + if (servers.indexOf(server) === -1) { + servers.push(server); + } + } + for (const server of servers) { + if (extension.recommendationSources && extension.recommendationSources.length) { + if (extension.recommendationSources.some(recommendationSource => this.getExtensionManagementServerForRecommendationSource(recommendationSource) === server)) { + return true; + } + } + } + } + return false; + } + + private getExtensionManagementServerForRecommendationSource(source: ExtensionRecommendationSource): IExtensionManagementServer { + if (source instanceof URI) { + return this.extensionManagementServerService.getExtensionManagementServer(source); + } + if (source === this.contextService.getWorkspace()) { + return this.extensionManagementServerService.getDefaultExtensionManagementServer(); + } + for (const workspaceFolder of this.contextService.getWorkspace().folders) { + if (source === workspaceFolder) { + return this.extensionManagementServerService.getExtensionManagementServer(workspaceFolder.uri); + } + } + return this.extensionManagementServerService.getDefaultExtensionManagementServer(); } private getWorkspaceRecommendationsModel(query: Query, options: IQueryOptions): TPromise> { const value = query.value.replace(/@recommended:workspace/g, '').trim().toLowerCase(); return this.tipsService.getWorkspaceRecommendations() .then(recommendations => { - const names = recommendations.filter(name => name.toLowerCase().indexOf(value) > -1); + const names = recommendations.map(({ extensionId }) => extensionId).filter(name => name.toLowerCase().indexOf(value) > -1); /* __GDPR__ "extensionWorkspaceRecommendations:open" : { "count" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } @@ -464,8 +503,8 @@ export class ExtensionsListView extends ViewletPanel { private getKeymapRecommendationsModel(query: Query, options: IQueryOptions): TPromise> { const value = query.value.replace(/@recommended:keymaps/g, '').trim().toLowerCase(); - const names = this.tipsService.getKeymapRecommendations() - .filter(name => name.toLowerCase().indexOf(value) > -1); + const names: string[] = this.tipsService.getKeymapRecommendations().map(({ extensionId }) => extensionId) + .filter(extensionId => extensionId.toLowerCase().indexOf(value) > -1); if (!names.length) { return TPromise.as(new PagedModel([])); @@ -484,18 +523,20 @@ export class ExtensionsListView extends ViewletPanel { } private setModel(model: IPagedModel) { - this.list.model = model; - this.list.scrollTop = 0; - const count = this.count(); + if (this.list) { + this.list.model = model; + this.list.scrollTop = 0; + const count = this.count(); - toggleClass(this.extensionsList, 'hidden', count === 0); - toggleClass(this.messageBox, 'hidden', count > 0); - this.badge.setCount(count); + toggleClass(this.extensionsList, 'hidden', count === 0); + toggleClass(this.messageBox, 'hidden', count > 0); + this.badge.setCount(count); - if (count === 0 && this.isVisible()) { - this.messageBox.textContent = localize('no extensions found', "No extensions found."); - } else { - this.messageBox.textContent = ''; + if (count === 0 && this.isVisible()) { + this.messageBox.textContent = localize('no extensions found', "No extensions found."); + } else { + this.messageBox.textContent = ''; + } } } @@ -533,8 +574,9 @@ export class ExtensionsListView extends ViewletPanel { } dispose(): void { - this.disposables = dispose(this.disposables); super.dispose(); + this.disposables = dispose(this.disposables); + this.list = null; } static isBuiltInExtensionsQuery(query: string): boolean { @@ -679,13 +721,14 @@ export class RecommendedExtensionsView extends ExtensionsListView { export class WorkspaceRecommendedExtensionsView extends ExtensionsListView { + private installAllAction: InstallWorkspaceRecommendedExtensionsAction; renderBody(container: HTMLElement): void { super.renderBody(container); - this.disposables.push(this.tipsService.onRecommendationChange(() => { - this.show(''); - })); + this.disposables.push(this.tipsService.onRecommendationChange(() => this.update())); + this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.setRecommendationsToInstall())); + this.disposables.push(this.contextService.onDidChangeWorkbenchState(() => this.update())); } renderHeader(container: HTMLElement): void { @@ -698,22 +741,37 @@ export class WorkspaceRecommendedExtensionsView extends ExtensionsListView { animated: false }); actionbar.onDidRun(({ error }) => error && this.notificationService.error(error)); - const installAllAction = this.instantiationService.createInstance(InstallWorkspaceRecommendedExtensionsAction, InstallWorkspaceRecommendedExtensionsAction.ID, InstallWorkspaceRecommendedExtensionsAction.LABEL); + + this.installAllAction = this.instantiationService.createInstance(InstallWorkspaceRecommendedExtensionsAction, InstallWorkspaceRecommendedExtensionsAction.ID, InstallWorkspaceRecommendedExtensionsAction.LABEL, []); const configureWorkspaceFolderAction = this.instantiationService.createInstance(ConfigureWorkspaceFolderRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction.ID, ConfigureWorkspaceFolderRecommendedExtensionsAction.LABEL); - installAllAction.class = 'octicon octicon-cloud-download'; + this.installAllAction.class = 'octicon octicon-cloud-download'; configureWorkspaceFolderAction.class = 'octicon octicon-pencil'; - actionbar.push([installAllAction], { icon: true, label: false }); + actionbar.push([this.installAllAction], { icon: true, label: false }); actionbar.push([configureWorkspaceFolderAction], { icon: true, label: false }); - this.disposables.push(actionbar); + this.disposables.push(...[this.installAllAction, configureWorkspaceFolderAction, actionbar]); } - async show(query: string): TPromise> { + async show(): TPromise> { let model = await super.show('@recommended:workspace'); this.setExpanded(model.length > 0); return model; } + private update(): void { + this.show(); + this.setRecommendationsToInstall(); + } + + private setRecommendationsToInstall(): TPromise { + return this.getRecommendationsToInstall() + .then(recommendations => { this.installAllAction.recommendations = recommendations; }); + } + + private getRecommendationsToInstall(): TPromise { + return this.tipsService.getWorkspaceRecommendations() + .then(recommendations => recommendations.filter(({ extensionId }) => this.extensionsWorkbenchService.local.some(i => areSameExtensions({ id: extensionId }, { id: i.id })))); + } } \ No newline at end of file diff --git a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts index a5bb118f14c..c8cf45c8644 100644 --- a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts +++ b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts @@ -18,7 +18,7 @@ import { IPager, mapPager, singlePagePager } from 'vs/base/common/paging'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IExtensionManagementService, IExtensionGalleryService, ILocalExtension, IGalleryExtension, IQueryOptions, IExtensionManifest, - InstallExtensionEvent, DidInstallExtensionEvent, LocalExtensionType, DidUninstallExtensionEvent, IExtensionEnablementService, IExtensionIdentifier, EnablementState, + InstallExtensionEvent, DidInstallExtensionEvent, LocalExtensionType, DidUninstallExtensionEvent, IExtensionEnablementService, IExtensionIdentifier, EnablementState, IExtensionTipsService, ExtensionRecommendationSource, IExtensionRecommendation } from 'vs/platform/extensionManagement/common/extensionManagement'; import { getGalleryExtensionIdFromLocal, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, areSameExtensions, getMaliciousExtensionsSet } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -48,6 +48,7 @@ class Extension implements IExtension { public get local(): ILocalExtension { return this.locals[0]; } public enablementState: EnablementState = EnablementState.Enabled; + public recommendationSources: ExtensionRecommendationSource[]; constructor( private galleryService: IExtensionGalleryService, @@ -350,7 +351,8 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService, @IWindowService private windowService: IWindowService, @ILogService private logService: ILogService, @IProgressService2 private progressService: IProgressService2, - @IExtensionService private runtimeExtensionService: IExtensionService + @IExtensionService private runtimeExtensionService: IExtensionService, + @IExtensionTipsService private extensionTipsService: IExtensionTipsService ) { this.stateProvider = ext => this.getExtensionState(ext); @@ -385,8 +387,8 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService, } queryLocal(): TPromise { - return this.extensionService.getInstalled() - .then(installed => this.getDistinctInstalledExtensions(installed) + return TPromise.join([this.extensionService.getInstalled(), this.extensionTipsService.getAllRecommendations()]) + .then(([installed, allRecommendations]) => this.getDistinctInstalledExtensions(installed) .then(distinctInstalled => { const installedById = index(this.installed, e => e.local.identifier.id); const groupById = groupBy(installed, i => getGalleryExtensionIdFromLocal(i)); @@ -397,6 +399,10 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService, const extension = installedById[local.identifier.id] || new Extension(this.galleryService, this.stateProvider, locals, null, this.telemetryService); extension.locals = locals; extension.enablementState = this.extensionEnablementService.getEnablementState(local); + const recommendation = allRecommendations.filter(r => areSameExtensions({ id: r.extensionId }, { id: extension.id }))[0]; + if (recommendation) { + extension.recommendationSources = recommendation.sources || []; + } return extension; }); @@ -406,19 +412,20 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService, } queryGallery(options: IQueryOptions = {}): TPromise> { - return this.extensionService.getExtensionsReport().then(report => { - const maliciousSet = getMaliciousExtensionsSet(report); + return TPromise.join([this.extensionTipsService.getAllRecommendations(), this.extensionService.getExtensionsReport()]) + .then(([allRecommendations, report]) => { + const maliciousSet = getMaliciousExtensionsSet(report); - return this.galleryService.query(options) - .then(result => mapPager(result, gallery => this.fromGallery(gallery, maliciousSet))) - .then(null, err => { - if (/No extension gallery service configured/.test(err.message)) { - return TPromise.as(singlePagePager([])); - } + return this.galleryService.query(options) + .then(result => mapPager(result, gallery => this.fromGallery(gallery, maliciousSet, allRecommendations))) + .then(null, err => { + if (/No extension gallery service configured/.test(err.message)) { + return TPromise.as(singlePagePager([])); + } - return TPromise.wrapError>(err); - }); - }); + return TPromise.wrapError>(err); + }); + }); } loadDependencies(extension: IExtension): TPromise { @@ -426,20 +433,21 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService, return TPromise.wrap(null); } - return this.extensionService.getExtensionsReport().then(report => { - const maliciousSet = getMaliciousExtensionsSet(report); + return TPromise.join([this.extensionTipsService.getAllRecommendations(), this.extensionService.getExtensionsReport()]) + .then(([allRecommendations, report]) => { + const maliciousSet = getMaliciousExtensionsSet(report); - return this.galleryService.loadAllDependencies((extension).dependencies.map(id => { id })) - .then(galleryExtensions => galleryExtensions.map(galleryExtension => this.fromGallery(galleryExtension, maliciousSet))) - .then(extensions => [...this.local, ...extensions]) - .then(extensions => { - const map = new Map(); - for (const extension of extensions) { - map.set(extension.id, extension); - } - return new ExtensionDependencies(extension, extension.id, map); - }); - }); + return this.galleryService.loadAllDependencies((extension).dependencies.map(id => { id })) + .then(galleryExtensions => galleryExtensions.map(galleryExtension => this.fromGallery(galleryExtension, maliciousSet, allRecommendations))) + .then(extensions => [...this.local, ...extensions]) + .then(extensions => { + const map = new Map(); + for (const extension of extensions) { + map.set(extension.id, extension); + } + return new ExtensionDependencies(extension, extension.id, map); + }); + }); } open(extension: IExtension, sideByside: boolean = false): TPromise { @@ -498,7 +506,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService, return false; } - private fromGallery(gallery: IGalleryExtension, maliciousExtensionSet: Set): Extension { + private fromGallery(gallery: IGalleryExtension, maliciousExtensionSet: Set, allRecommendations: IExtensionRecommendation[]): Extension { let result = this.getInstalledExtensionMatchingGallery(gallery); if (result) { @@ -518,6 +526,11 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService, result.isMalicious = true; } + const recommendation = allRecommendations.filter(r => areSameExtensions({ id: r.extensionId }, { id: result.id }))[0]; + if (recommendation) { + result.recommendationSources = recommendation.sources || []; + } + return result; } diff --git a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsTipsService.test.ts b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsTipsService.test.ts index 5299d419c58..7f071a151aa 100644 --- a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsTipsService.test.ts +++ b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsTipsService.test.ts @@ -506,9 +506,9 @@ suite('ExtensionsTipsService Test', () => { return testObject.loadRecommendationsPromise.then(() => { const recommendations = testObject.getFileBasedRecommendations(); assert.equal(recommendations.length, 2); - assert.ok(recommendations.indexOf('ms-vscode.csharp') > -1); // stored recommendation that exists in product.extensionTips - assert.ok(recommendations.indexOf('ms-python.python') > -1); // stored recommendation that exists in product.extensionImportantTips - assert.ok(recommendations.indexOf('eg2.tslint') === -1); // stored recommendation that is no longer in neither product.extensionTips nor product.extensionImportantTips + assert.ok(recommendations.some(({ extensionId }) => extensionId === 'ms-vscode.csharp')); // stored recommendation that exists in product.extensionTips + assert.ok(recommendations.some(({ extensionId }) => extensionId === 'ms-python.python')); // stored recommendation that exists in product.extensionImportantTips + assert.ok(recommendations.every(({ extensionId }) => extensionId !== 'eg2.tslint')); // stored recommendation that is no longer in neither product.extensionTips nor product.extensionImportantTips }); }); }); @@ -525,10 +525,10 @@ suite('ExtensionsTipsService Test', () => { return testObject.loadRecommendationsPromise.then(() => { const recommendations = testObject.getFileBasedRecommendations(); assert.equal(recommendations.length, 2); - assert.ok(recommendations.indexOf('ms-vscode.csharp') > -1); // stored recommendation that exists in product.extensionTips - assert.ok(recommendations.indexOf('ms-python.python') > -1); // stored recommendation that exists in product.extensionImportantTips - assert.ok(recommendations.indexOf('eg2.tslint') === -1); // stored recommendation that is no longer in neither product.extensionTips nor product.extensionImportantTips - assert.ok(recommendations.indexOf('lukehoban.Go') === -1); //stored recommendation that is older than a week + assert.ok(recommendations.some(({ extensionId }) => extensionId === 'ms-vscode.csharp')); // stored recommendation that exists in product.extensionTips + assert.ok(recommendations.some(({ extensionId }) => extensionId === 'ms-python.python')); // stored recommendation that exists in product.extensionImportantTips + assert.ok(recommendations.every(({ extensionId }) => extensionId !== 'eg2.tslint')); // stored recommendation that is no longer in neither product.extensionTips nor product.extensionImportantTips + assert.ok(recommendations.every(({ extensionId }) => extensionId !== 'lukehoban.Go')); //stored recommendation that is older than a week }); }); }); From bd7d264786a72a2309b91639f44478eca6cf9f34 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 25 Jun 2018 12:06:43 +0200 Subject: [PATCH 010/228] Support uninstall action for multiple extension management servers --- .../electron-browser/extensionEditor.ts | 2 +- .../electron-browser/extensionsActions.ts | 249 +++++++++++++++--- .../electron-browser/extensionsList.ts | 2 +- 3 files changed, 220 insertions(+), 33 deletions(-) diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts index 3661e041062..966191a9793 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts @@ -370,7 +370,7 @@ export class ExtensionEditor extends BaseEditor { const maliciousStatusAction = this.instantiationService.createInstance(MaliciousStatusLabelAction, true); const disabledStatusAction = this.instantiationService.createInstance(DisabledStatusLabelAction); - const installAction = servers.length === 1 ? this.instantiationService.createInstance(CombinedInstallAction) : this.instantiationService.createInstance(MultiServerInstallAction); + const installAction = this.instantiationService.createInstance(CombinedInstallAction); const updateAction = servers.length === 1 ? this.instantiationService.createInstance(UpdateAction) : this.instantiationService.createInstance(MultiServerUpdateAction); const enableAction = this.instantiationService.createInstance(EnableAction); const disableAction = this.instantiationService.createInstance(DisableAction); diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts index dc54ae61d80..0ef379a0938 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts @@ -51,6 +51,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; import { ExtensionsInput } from 'vs/workbench/parts/extensions/common/extensionsInput'; import product from 'vs/platform/node/product'; +import { ContextSubMenu } from 'vs/base/browser/contextmenu'; const promptDownloadManually = (extension: IGalleryExtension, message: string, instantiationService: IInstantiationService, notificationService: INotificationService, openerService: IOpenerService) => { const downloadUrl = `${product.extensionsGallery.serviceUrl}/publishers/${extension.publisher}/vsextensions/${extension.name}/${extension.version}/vspackage`; @@ -231,8 +232,8 @@ export class UninstallAction extends Action { export class CombinedInstallAction extends Action { private static readonly NoExtensionClass = 'extension-action prominent install no-extension'; - private installAction: InstallAction; - private uninstallAction: UninstallAction; + private installAction: MultiServerInstallAction | InstallAction; + private uninstallAction: MultiServerUninstallAction | UninstallAction; private disposables: IDisposable[] = []; private _extension: IExtension; get extension(): IExtension { return this._extension; } @@ -243,12 +244,13 @@ export class CombinedInstallAction extends Action { } constructor( - @IInstantiationService instantiationService: IInstantiationService + @IInstantiationService instantiationService: IInstantiationService, + @IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService ) { super('extensions.combinedInstall', '', '', false); - this.installAction = instantiationService.createInstance(InstallAction); - this.uninstallAction = instantiationService.createInstance(UninstallAction); + this.installAction = extensionManagementServerService.extensionManagementServers.length > 1 ? instantiationService.createInstance(MultiServerInstallAction, false) : instantiationService.createInstance(InstallAction); + this.uninstallAction = extensionManagementServerService.extensionManagementServers.length > 1 ? instantiationService.createInstance(MultiServerUninstallAction) : instantiationService.createInstance(UninstallAction); this.disposables.push(this.installAction, this.uninstallAction); this.installAction.onDidChange(this.update, this, this.disposables); @@ -403,6 +405,28 @@ export class InstallGalleryExtensionAction extends Action { } } +export class UninstallExtensionAction extends Action { + + private _server: IExtensionManagementServer; + private _extension: ILocalExtension; + get extension(): ILocalExtension { return this._extension; } + set extension(extension: ILocalExtension) { this._extension = extension; this.enabled = !!this._extension; } + + constructor( + id: string, label: string, server: IExtensionManagementServer, + ) { + super(id, label, null, false); + this._server = server; + } + + run(): TPromise { + if (this.extension) { + return this._server.extensionManagementService.uninstall(this.extension); + } + return TPromise.as(null); + } +} + export class UpdateGalleryExtensionAction extends Action { private server: IExtensionManagementServer; @@ -449,25 +473,30 @@ export class MultiServerInstallAction extends Action { private static readonly Class = 'extension-action multiserver prominent install'; private static readonly InstallingClass = 'extension-action multiserver install installing'; - private _installActions: InstallGalleryExtensionAction[] = []; + private readonly disableWhenInstalled: boolean; + + readonly actions: InstallGalleryExtensionAction[] = []; private _actionItem: DropDownMenuActionItem; get actionItem(): IActionItem { return this._actionItem; } - private disposables: IDisposable[] = []; private _extension: IExtension; get extension(): IExtension { return this._extension; } set extension(extension: IExtension) { this._extension = extension; this.update(); } + private disposables: IDisposable[] = []; + constructor( + disableWhenInstalled: boolean, @IExtensionManagementServerService private extensionManagementServerService: IExtensionManagementServerService, @IInstantiationService private instantiationService: IInstantiationService, @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, @IWorkspaceContextService private contextService: IWorkspaceContextService ) { super(MultiServerInstallAction.ID, MultiServerInstallAction.InstallLabel, MultiServerInstallAction.Class, false); - this._installActions = this.extensionManagementServerService.extensionManagementServers.map(server => this.instantiationService.createInstance(InstallGalleryExtensionAction, `extensions.install.${server.location.authority}`, localize('installInServer', "{0}", server.location.authority), server)); - this._actionItem = this.instantiationService.createInstance(DropDownMenuActionItem, this, [this._installActions]); - this.disposables.push(...[this._actionItem, ...this._installActions]); + this.disableWhenInstalled = disableWhenInstalled; + this.actions = this.extensionManagementServerService.extensionManagementServers.map(server => this.instantiationService.createInstance(InstallGalleryExtensionAction, `extensions.install.${server.location.authority}`, localize('installInServer', "{0}", server.location.authority), server)); + this._actionItem = this.instantiationService.createInstance(DropDownMenuActionItem, this, [this.actions]); + this.disposables.push(...[this._actionItem, ...this.actions]); this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.extension = this.extension ? this.extensionsWorkbenchService.local.filter(l => areSameExtensions({ id: l.id }, { id: this.extension.id }))[0] : this.extension)); this.update(); } @@ -480,12 +509,19 @@ export class MultiServerInstallAction extends Action { return; } + const isInstalled = this.extension.locals.length > 0; + + if (isInstalled && this.disableWhenInstalled) { + this.enabled = false; + return; + } + let isExtensionNotInstalledInRecommendedServer: boolean = false; - this._installActions.forEach((installAction, index) => { + this.actions.forEach((installAction, index) => { const server = this.extensionManagementServerService.extensionManagementServers[index]; installAction.extension = this.extension.gallery; installAction.label = localize('installInServer', "{0}", server.location.authority); - installAction.enabled = !this.extension.locals.some(local => this.extensionManagementServerService.getExtensionManagementServer(local.location) === server); + installAction.enabled = this.extension.gallery && !this.extension.locals.some(local => this.extensionManagementServerService.getExtensionManagementServer(local.location) === server); if (this.extension.recommendationSources && this.extension.recommendationSources.length) { if (this.extension.recommendationSources.some(recommendationSource => getExtensionManagementServerForRecommendationSource(recommendationSource, this.extensionManagementServerService, this.contextService) === server)) { installAction.label = localize('installInRecommendedServer', "{0} (Recommended)", server.location.authority); @@ -518,6 +554,68 @@ export class MultiServerInstallAction extends Action { } } +export class MultiServerInstallSubMenuAction extends ContextSubMenu { + + private readonly action: MultiServerInstallAction; + private disposables: IDisposable[] = []; + + private _extension: IExtension; + get extension(): IExtension { return this._extension; } + set extension(extension: IExtension) { this._extension = extension; this.action.extension = extension; } + + constructor( + @IInstantiationService instantiationService: IInstantiationService, + ) { + super('', []); + this.action = instantiationService.createInstance(MultiServerInstallAction, false); + this.disposables.push(this.action); + this.entries = this.action.actions; + this.disposables.push(this.onDidChange(() => this.update())); + this.update(); + } + + private update(): void { + this.label = this.action.label; + this.enabled = this.action.enabled; + } + + dispose(): void { + super.dispose(); + this.disposables = dispose(this.disposables); + } +} + +export class MultiServerUnInstallSubMenuAction extends ContextSubMenu { + + private readonly action: MultiServerUninstallAction; + private disposables: IDisposable[] = []; + + private _extension: IExtension; + get extension(): IExtension { return this._extension; } + set extension(extension: IExtension) { this._extension = extension; this.action.extension = extension; } + + constructor( + @IInstantiationService instantiationService: IInstantiationService, + ) { + super('', []); + this.action = instantiationService.createInstance(MultiServerUninstallAction); + this.disposables.push(this.action); + this.entries = this.action.actions; + this.disposables.push(this.onDidChange(() => this.update())); + this.update(); + } + + private update(): void { + this.label = this.action.label; + this.enabled = this.action.enabled; + } + + dispose(): void { + super.dispose(); + this.disposables = dispose(this.disposables); + } +} + export class MultiServerUpdateAction extends Action { static ID: string = 'extensions.multiserver.update'; @@ -570,6 +668,87 @@ export class MultiServerUpdateAction extends Action { } } +export class MultiServerUninstallAction extends Action { + + static ID: string = 'extensions.multiserver.uninstall'; + + private static readonly UninstallLabel = localize('uninstallAction', "Uninstall"); + private static readonly UninstallingLabel = localize('Uninstalling', "Uninstalling"); + + private static readonly UninstallClass = 'extension-action uninstall'; + private static readonly UnInstallingClass = 'extension-action uninstall uninstalling'; + + readonly actions: UninstallExtensionAction[] = []; + private _actionItem: DropDownMenuActionItem; + get actionItem(): IActionItem { return this._actionItem; } + + private _extension: IExtension; + get extension(): IExtension { return this._extension; } + set extension(extension: IExtension) { this._extension = extension; this.update(); } + + private disposables: IDisposable[] = []; + + constructor( + @IExtensionManagementServerService private extensionManagementServerService: IExtensionManagementServerService, + @IInstantiationService private instantiationService: IInstantiationService, + @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, + ) { + super(MultiServerUninstallAction.ID, MultiServerUninstallAction.UninstallLabel, MultiServerUninstallAction.UninstallClass, false); + this.actions = this.extensionManagementServerService.extensionManagementServers.map(server => this.instantiationService.createInstance(UninstallExtensionAction, `extensions.uninstall.${server.location.authority}`, server.location.authority, server)); + this._actionItem = this.instantiationService.createInstance(DropDownMenuActionItem, this, [this.actions]); + this.disposables.push(...[this._actionItem, ...this.actions]); + this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.extension = this.extension ? this.extensionsWorkbenchService.local.filter(l => areSameExtensions({ id: l.id }, { id: this.extension.id }))[0] : this.extension)); + this.update(); + } + + private update(): void { + if (!this.extension) { + this.enabled = false; + } else { + const state = this.extension.state; + + if (state === ExtensionState.Uninstalling) { + this.label = MultiServerUninstallAction.UninstallingLabel; + this.class = MultiServerUninstallAction.UnInstallingClass; + this.enabled = false; + return; + } + + this.label = MultiServerUninstallAction.UninstallLabel; + this.class = MultiServerUninstallAction.UninstallClass; + + const installedExtensions = this.extensionsWorkbenchService.local.filter(e => e.id === this.extension.id); + + if (!installedExtensions.length) { + this.enabled = false; + return; + } + + if (installedExtensions[0].type !== LocalExtensionType.User) { + this.enabled = false; + return; + } + + this.enabled = true; + + this.actions.forEach((installAction, index) => { + const server = this.extensionManagementServerService.extensionManagementServers[index]; + installAction.extension = this.extension.locals.filter(local => this.extensionManagementServerService.getExtensionManagementServer(local.location) === server)[0]; + }); + } + } + + public run(): TPromise { + this._actionItem.showMenu(); + return TPromise.wrap(null); + } + + dispose(): void { + super.dispose(); + this.disposables = dispose(this.disposables); + } +} + export class DropDownMenuActionItem extends ActionItem { private disposables: IDisposable[] = []; @@ -628,35 +807,36 @@ export class ManageExtensionAction extends Action { constructor( @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, - @IInstantiationService private instantiationService: IInstantiationService + @IInstantiationService private instantiationService: IInstantiationService, + @IExtensionManagementServerService private extensionManagmentServerService: IExtensionManagementServerService ) { super(ManageExtensionAction.ID); - this._actionItem = this.instantiationService.createInstance(DropDownMenuActionItem, this, [ - [ - instantiationService.createInstance(EnableGloballyAction, EnableGloballyAction.LABEL), - instantiationService.createInstance(EnableForWorkspaceAction, EnableForWorkspaceAction.LABEL) - ], - [ - instantiationService.createInstance(DisableGloballyAction, DisableGloballyAction.LABEL), - instantiationService.createInstance(DisableForWorkspaceAction, DisableForWorkspaceAction.LABEL) - ], - [ - instantiationService.createInstance(UninstallAction) - ] - ]); + this._actionItem = this.instantiationService.createInstance(DropDownMenuActionItem, this, this.createMenuActionGroups()); this.disposables.push(this._actionItem); this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update())); this.update(); } + private createMenuActionGroups(): IAction[][] { + return [ + [ + this.instantiationService.createInstance(EnableGloballyAction, EnableGloballyAction.LABEL), + this.instantiationService.createInstance(EnableForWorkspaceAction, EnableForWorkspaceAction.LABEL) + ], + [ + this.instantiationService.createInstance(DisableGloballyAction, DisableGloballyAction.LABEL), + this.instantiationService.createInstance(DisableForWorkspaceAction, DisableForWorkspaceAction.LABEL) + ], + this.extensionManagmentServerService.extensionManagementServers.length > 1 ? [this.instantiationService.createInstance(MultiServerInstallSubMenuAction)] : [], + [ + this.extensionManagmentServerService.extensionManagementServers.length > 1 ? this.instantiationService.createInstance(MultiServerUnInstallSubMenuAction) : this.instantiationService.createInstance(UninstallAction) + ] + ]; + } + private update(): void { - for (const actions of this._actionItem.menuActionGroups) { - for (const action of actions) { - (action).extension = this.extension; - } - } this.class = ManageExtensionAction.HideManageExtensionClass; this.tooltip = ''; this.enabled = false; @@ -666,6 +846,13 @@ export class ManageExtensionAction extends Action { this.class = this.enabled || state === ExtensionState.Uninstalling ? ManageExtensionAction.Class : ManageExtensionAction.HideManageExtensionClass; this.tooltip = state === ExtensionState.Uninstalling ? localize('ManageExtensionAction.uninstallingTooltip', "Uninstalling") : ''; } + const menuActionGroups = this.createMenuActionGroups(); + for (const actions of menuActionGroups) { + for (const action of actions) { + (action).extension = this.extension; + } + } + this._actionItem.menuActionGroups = menuActionGroups; } public run(): TPromise { diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsList.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsList.ts index 598208decd1..8dcd8b1d2b1 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsList.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsList.ts @@ -107,7 +107,7 @@ export class Renderer implements IPagedRenderer { const maliciousStatusAction = this.instantiationService.createInstance(MaliciousStatusLabelAction, false); const disabledStatusAction = this.instantiationService.createInstance(DisabledStatusLabelAction); const installAction = this.extensionManagementServerService.extensionManagementServers.length === 1 ? this.instantiationService.createInstance(InstallAction) - : this.instantiationService.createInstance(MultiServerInstallAction); + : this.instantiationService.createInstance(MultiServerInstallAction, true); const updateAction = this.extensionManagementServerService.extensionManagementServers.length === 1 ? this.instantiationService.createInstance(UpdateAction) : this.instantiationService.createInstance(MultiServerUpdateAction); const reloadAction = this.instantiationService.createInstance(ReloadAction); From a28cde1bf73270e2ff58efe48689ff2dd70bfae2 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 25 Jun 2018 12:24:18 +0200 Subject: [PATCH 011/228] Fix tests --- .../test/electron-browser/extensionsActions.test.ts | 3 +-- .../test/electron-browser/extensionsTipsService.test.ts | 7 +------ .../electron-browser/extensionsWorkbenchService.test.ts | 3 +-- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsActions.test.ts b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsActions.test.ts index 68779a8ef72..8308b8fc640 100644 --- a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsActions.test.ts +++ b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsActions.test.ts @@ -74,8 +74,7 @@ suite('ExtensionsActions Test', () => { instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - instantiationService.stub(IExtensionTipsService, ExtensionTipsService); - instantiationService.stub(IExtensionTipsService, 'getKeymapRecommendations', () => []); + instantiationService.set(IExtensionTipsService, instantiationService.createInstance(ExtensionTipsService)); instantiationService.stub(IURLService, URLService); }); diff --git a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsTipsService.test.ts b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsTipsService.test.ts index 7f071a151aa..1aafa089302 100644 --- a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsTipsService.test.ts +++ b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsTipsService.test.ts @@ -38,9 +38,8 @@ import { assign } from 'vs/base/common/objects'; import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { IExtensionsWorkbenchService, ConfigurationKey } from 'vs/workbench/parts/extensions/common/extensions'; +import { ConfigurationKey } from 'vs/workbench/parts/extensions/common/extensions'; import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; -import { ExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/node/extensionsWorkbenchService'; import { TestExtensionEnablementService } from 'vs/platform/extensionManagement/test/common/extensionEnablementService.test'; import { IURLService } from 'vs/platform/url/common/url'; import product from 'vs/platform/node/product'; @@ -167,7 +166,6 @@ function aGalleryExtension(name: string, properties: any = {}, galleryExtensionP suite('ExtensionsTipsService Test', () => { let workspaceService: IWorkspaceContextService; let instantiationService: TestInstantiationService; - let extensionsWorkbenchService: IExtensionsWorkbenchService; let testConfigurationService: TestConfigurationService; let testObject: ExtensionTipsService; let parentResource: string; @@ -222,8 +220,6 @@ suite('ExtensionsTipsService Test', () => { instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', []); instantiationService.stub(IExtensionGalleryService, 'isEnabled', true); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(...mockExtensionGallery)); - extensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); - instantiationService.stub(IExtensionsWorkbenchService, extensionsWorkbenchService); prompted = false; @@ -246,7 +242,6 @@ suite('ExtensionsTipsService Test', () => { teardown(done => { (testObject).dispose(); - (extensionsWorkbenchService).dispose(); if (parentResource) { extfs.del(parentResource, os.tmpdir(), () => { }, done); } else { diff --git a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts index f1f0435cf31..2b3f43d0114 100644 --- a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts +++ b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts @@ -76,8 +76,7 @@ suite('ExtensionsWorkbenchService Test', () => { instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - instantiationService.stub(IExtensionTipsService, ExtensionTipsService); - instantiationService.stub(IExtensionTipsService, 'getKeymapRecommendations', () => []); + instantiationService.set(IExtensionTipsService, instantiationService.createInstance(ExtensionTipsService)); instantiationService.stub(INotificationService, { prompt: () => null }); instantiationService.stub(IDialogService, { show: () => TPromise.as(0) }); From 5e0cd08dcba7ca9f6000a8d3788a144104cdf7f8 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 18 Jun 2018 19:07:07 +0200 Subject: [PATCH 012/228] selection change event --- src/vs/vscode.d.ts | 17 +++++++++++++++++ src/vs/workbench/api/node/extHostTreeViews.ts | 14 +++++++++++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 6b291af09fa..05efdffb630 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -6159,6 +6159,18 @@ declare module 'vscode' { } + /** + * The event that is fired when there is a change in [tree view's selections](#TreeView.selections) + */ + export interface TreeViewSelectionChangeEvent { + + /** + * Selected elements. + */ + selections: T[]; + + } + /** * Represents a Tree view */ @@ -6174,6 +6186,11 @@ declare module 'vscode' { */ readonly onDidCollapseElement: Event>; + /** + * Event that is fired when the selection has changed + */ + readonly onDidChangeSelection: Event>; + /** * Currently selected elements. */ diff --git a/src/vs/workbench/api/node/extHostTreeViews.ts b/src/vs/workbench/api/node/extHostTreeViews.ts index 499e859b46f..4287ad62e93 100644 --- a/src/vs/workbench/api/node/extHostTreeViews.ts +++ b/src/vs/workbench/api/node/extHostTreeViews.ts @@ -17,6 +17,7 @@ import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/node/extHos import { asWinJsPromise } from 'vs/base/common/async'; import { TreeItemCollapsibleState, ThemeIcon } from 'vs/workbench/api/node/extHostTypes'; import { isUndefinedOrNull } from 'vs/base/common/types'; +import { equals } from 'vs/base/common/arrays'; type TreeItemHandle = string; @@ -51,6 +52,7 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape { return { get onDidCollapseElement() { return treeView.onDidCollapseElement; }, get onDidExpandElement() { return treeView.onDidExpandElement; }, + get onDidChangeSelection() { return treeView.onDidChangeSelection; }, get selection() { return treeView.selectedElements; }, reveal: (element: T, options?: { select?: boolean }): Thenable => { return treeView.reveal(element, options); @@ -113,8 +115,8 @@ class ExtHostTreeView extends Disposable { private elements: Map = new Map(); private nodes: Map = new Map(); - private _selectedElements: T[] = []; - get selectedElements(): T[] { return this._selectedElements; } + private _selectedHandles: TreeItemHandle[] = []; + get selectedElements(): T[] { return this._selectedHandles.map(handle => this.getExtensionElement(handle)).filter(element => !isUndefinedOrNull(element)); } private _onDidExpandElement: Emitter> = this._register(new Emitter>()); readonly onDidExpandElement: Event> = this._onDidExpandElement.event; @@ -122,6 +124,9 @@ class ExtHostTreeView extends Disposable { private _onDidCollapseElement: Emitter> = this._register(new Emitter>()); readonly onDidCollapseElement: Event> = this._onDidCollapseElement.event; + private _onDidChangeSelection: Emitter> = this._register(new Emitter>()); + readonly onDidChangeSelection: Event> = this._onDidChangeSelection.event; + private refreshPromise: TPromise = TPromise.as(null); constructor(private viewId: string, private dataProvider: vscode.TreeDataProvider, private proxy: MainThreadTreeViewsShape, private commands: CommandsConverter) { @@ -182,7 +187,10 @@ class ExtHostTreeView extends Disposable { } setSelection(treeItemHandles: TreeItemHandle[]): void { - this._selectedElements = treeItemHandles.map(handle => this.getExtensionElement(handle)).filter(element => !isUndefinedOrNull(element)); + if (!equals(this._selectedHandles, treeItemHandles)) { + this._selectedHandles = treeItemHandles; + this._onDidChangeSelection.fire({ selections: this.selectedElements }); + } } private resolveUnknownParentChain(element: T): TPromise { From 752db2bacc8a28254c9ffc77e795a48bc139232d Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 19 Jun 2018 11:34:01 +0200 Subject: [PATCH 013/228] Add support for visibility and focus --- src/vs/vscode.d.ts | 41 ++++++++++++++----- .../electron-browser/mainThreadTreeViews.ts | 5 ++- src/vs/workbench/api/node/extHost.protocol.ts | 3 +- src/vs/workbench/api/node/extHostTreeViews.ts | 37 ++++++++++++++--- .../browser/parts/views/customView.ts | 17 ++++++-- .../browser/parts/views/viewsViewlet.ts | 4 +- src/vs/workbench/common/views.ts | 2 + 7 files changed, 87 insertions(+), 22 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 05efdffb630..4b80158046c 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -6160,14 +6160,23 @@ declare module 'vscode' { } /** - * The event that is fired when there is a change in [tree view's selections](#TreeView.selections) + * The event that is fired when there is a change in [tree view's selection](#TreeView.selection) */ export interface TreeViewSelectionChangeEvent { /** * Selected elements. */ - selections: T[]; + selection: T[]; + + } + + export interface TreeViewVisibilityChangeEvent { + + /** + * `true` if the [tree view](#TreeView) is visible otherwise `false`. + */ + visible: boolean; } @@ -6186,27 +6195,39 @@ declare module 'vscode' { */ readonly onDidCollapseElement: Event>; - /** - * Event that is fired when the selection has changed - */ - readonly onDidChangeSelection: Event>; - /** * Currently selected elements. */ readonly selection: ReadonlyArray; /** - * Reveal an element. By default revealed element is selected. + * Event that is fired when the [selection](#TreeView.selection) has changed + */ + readonly onDidChangeSelection: Event>; + + /** + * `true` if the [tree view](#TreeView) is visible otherwise `false`. + */ + readonly visible: boolean; + + /** + * Event that is fired when [visibility](TreeView.visible) has changed + */ + readonly onDidChangeVisibility: Event; + + /** + * Reveals the given element in the tree view. + * If the tree view is not visible then the element is revealed after the tree view is shown. * + * By default revealed element is selected and not focused. * In order to not to select, set the option `select` to `false`. + * In order to focus, set the option `focus` to `true`. * * **NOTE:** [TreeDataProvider](#TreeDataProvider) is required to implement [getParent](#TreeDataProvider.getParent) method to access this API. */ - reveal(element: T, options?: { select?: boolean }): Thenable; + reveal(element: T, options?: { select?: boolean, focus?: boolean }): Thenable; } - /** * A data provider that provides tree data */ diff --git a/src/vs/workbench/api/electron-browser/mainThreadTreeViews.ts b/src/vs/workbench/api/electron-browser/mainThreadTreeViews.ts index bf456a9571e..f10dd9cc5a0 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadTreeViews.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadTreeViews.ts @@ -39,8 +39,8 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie } } - $reveal(treeViewId: string, item: ITreeItem, parentChain: ITreeItem[], options?: { select?: boolean }): TPromise { - return this.viewsService.openView(treeViewId) + $reveal(treeViewId: string, item: ITreeItem, parentChain: ITreeItem[], options: { select: boolean, focus: boolean }): TPromise { + return this.viewsService.openView(treeViewId, options.focus) .then(() => { const viewer = this.getTreeViewer(treeViewId); return viewer ? viewer.reveal(item, parentChain, options) : null; @@ -61,6 +61,7 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie this._register(treeViewer.onDidExpandItem(item => this._proxy.$setExpanded(treeViewId, item.handle, true))); this._register(treeViewer.onDidCollapseItem(item => this._proxy.$setExpanded(treeViewId, item.handle, false))); this._register(treeViewer.onDidChangeSelection(items => this._proxy.$setSelection(treeViewId, items.map(({ handle }) => handle)))); + this._register(treeViewer.onDidChangeVisibility(isVisible => this._proxy.$setVisible(treeViewId, isVisible))); } private getTreeViewer(treeViewId: string): ITreeViewer { diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index f1b585d490b..6aab73ca6f7 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -216,7 +216,7 @@ export interface MainThreadTextEditorsShape extends IDisposable { export interface MainThreadTreeViewsShape extends IDisposable { $registerTreeViewDataProvider(treeViewId: string): void; $refresh(treeViewId: string, itemsToRefresh?: { [treeItemHandle: string]: ITreeItem }): TPromise; - $reveal(treeViewId: string, treeItem: ITreeItem, parentChain: ITreeItem[], options?: { select?: boolean }): TPromise; + $reveal(treeViewId: string, treeItem: ITreeItem, parentChain: ITreeItem[], options: { select: boolean, focus: boolean }): TPromise; } export interface MainThreadErrorsShape extends IDisposable { @@ -666,6 +666,7 @@ export interface ExtHostTreeViewsShape { $getChildren(treeViewId: string, treeItemHandle?: string): TPromise; $setExpanded(treeViewId: string, treeItemHandle: string, expanded: boolean): void; $setSelection(treeViewId: string, treeItemHandles: string[]): void; + $setVisible(treeViewId: string, visible: boolean): void; } export interface ExtHostWorkspaceShape { diff --git a/src/vs/workbench/api/node/extHostTreeViews.ts b/src/vs/workbench/api/node/extHostTreeViews.ts index 4287ad62e93..2df9b9076cd 100644 --- a/src/vs/workbench/api/node/extHostTreeViews.ts +++ b/src/vs/workbench/api/node/extHostTreeViews.ts @@ -52,9 +52,11 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape { return { get onDidCollapseElement() { return treeView.onDidCollapseElement; }, get onDidExpandElement() { return treeView.onDidExpandElement; }, - get onDidChangeSelection() { return treeView.onDidChangeSelection; }, get selection() { return treeView.selectedElements; }, - reveal: (element: T, options?: { select?: boolean }): Thenable => { + get onDidChangeSelection() { return treeView.onDidChangeSelection; }, + get visible() { return treeView.visible; }, + get onDidChangeVisibility() { return treeView.onDidChangeVisibility; }, + reveal: (element: T, options?: { select?: boolean, focus?: boolean }): Thenable => { return treeView.reveal(element, options); }, dispose: () => { @@ -88,6 +90,14 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape { treeView.setSelection(treeItemHandles); } + $setVisible(treeViewId: string, isVisible: boolean): void { + const treeView = this.treeViews.get(treeViewId); + if (!treeView) { + throw new Error(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', treeViewId)); + } + treeView.setVisible(isVisible); + } + private createExtHostTreeViewer(id: string, dataProvider: vscode.TreeDataProvider): ExtHostTreeView { const treeView = new ExtHostTreeView(id, dataProvider, this._proxy, this.commands.converter); this.treeViews.set(id, treeView); @@ -115,6 +125,9 @@ class ExtHostTreeView extends Disposable { private elements: Map = new Map(); private nodes: Map = new Map(); + private _visible: boolean = true; + get visible(): boolean { return this._visible; } + private _selectedHandles: TreeItemHandle[] = []; get selectedElements(): T[] { return this._selectedHandles.map(handle => this.getExtensionElement(handle)).filter(element => !isUndefinedOrNull(element)); } @@ -127,6 +140,9 @@ class ExtHostTreeView extends Disposable { private _onDidChangeSelection: Emitter> = this._register(new Emitter>()); readonly onDidChangeSelection: Event> = this._onDidChangeSelection.event; + private _onDidChangeVisibility: Emitter = this._register(new Emitter()); + readonly onDidChangeVisibility: Event = this._onDidChangeVisibility.event; + private refreshPromise: TPromise = TPromise.as(null); constructor(private viewId: string, private dataProvider: vscode.TreeDataProvider, private proxy: MainThreadTreeViewsShape, private commands: CommandsConverter) { @@ -165,14 +181,18 @@ class ExtHostTreeView extends Disposable { return this.elements.get(treeItemHandle); } - reveal(element: T, options?: { select?: boolean }): TPromise { + reveal(element: T, options?: { select?: boolean, focus?: boolean }): TPromise { + options = options ? options : { select: true, focus: false }; + const select = isUndefinedOrNull(options.select) ? true : options.select; + const focus = isUndefinedOrNull(options.focus) ? false : options.focus; + if (typeof this.dataProvider.getParent !== 'function') { return TPromise.wrapError(new Error(`Required registered TreeDataProvider to implement 'getParent' method to access 'reveal' method`)); } return this.refreshPromise .then(() => this.resolveUnknownParentChain(element)) .then(parentChain => this.resolveTreeNode(element, parentChain[parentChain.length - 1]) - .then(treeNode => this.proxy.$reveal(this.viewId, treeNode.item, parentChain.map(p => p.item), options))); + .then(treeNode => this.proxy.$reveal(this.viewId, treeNode.item, parentChain.map(p => p.item), { select, focus }))); } setExpanded(treeItemHandle: TreeItemHandle, expanded: boolean): void { @@ -189,7 +209,14 @@ class ExtHostTreeView extends Disposable { setSelection(treeItemHandles: TreeItemHandle[]): void { if (!equals(this._selectedHandles, treeItemHandles)) { this._selectedHandles = treeItemHandles; - this._onDidChangeSelection.fire({ selections: this.selectedElements }); + this._onDidChangeSelection.fire({ selection: this.selectedElements }); + } + } + + setVisible(visible: boolean): void { + if (visible !== this._visible) { + this._visible = visible; + this._onDidChangeVisibility.fire({ visible: this._visible }); } } diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/customView.ts index 5a63ac0a044..1de55d78e61 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/customView.ts @@ -193,6 +193,9 @@ export class CustomTreeViewer extends Disposable implements ITreeViewer { private _onDidChangeSelection: Emitter = this._register(new Emitter()); readonly onDidChangeSelection: Event = this._onDidChangeSelection.event; + private _onDidChangeVisibility: Emitter = this._register(new Emitter()); + readonly onDidChangeVisibility: Event = this._onDidChangeVisibility.event; + constructor( private id: string, private container: ViewContainer, @@ -267,6 +270,8 @@ export class CustomTreeViewer extends Disposable implements ITreeViewer { this.elementsToRefresh = []; } } + + this._onDidChangeVisibility.fire(this.isVisible); } focus(): void { @@ -337,13 +342,15 @@ export class CustomTreeViewer extends Disposable implements ITreeViewer { return TPromise.as(null); } - reveal(item: ITreeItem, parentChain: ITreeItem[], options?: { select?: boolean }): TPromise { + reveal(item: ITreeItem, parentChain: ITreeItem[], options?: { select?: boolean, focus?: boolean }): TPromise { if (this.tree && this.isVisible) { - options = options ? options : { select: true }; + options = options ? options : { select: false, focus: false }; + const select = isUndefinedOrNull(options.select) ? false : options.select; + const focus = isUndefinedOrNull(options.focus) ? false : options.focus; + const root: Root = this.tree.getInput(); const promise = root.children ? TPromise.as(null) : this.refresh(); // Refresh if root is not populated return promise.then(() => { - const select = isUndefinedOrNull(options.select) ? true : options.select; var result = TPromise.as(null); parentChain.forEach((e) => { result = result.then(() => this.tree.expand(e)); @@ -353,6 +360,10 @@ export class CustomTreeViewer extends Disposable implements ITreeViewer { if (select) { this.tree.setSelection([item], { source: 'api' }); } + if (focus) { + this.focus(); + this.tree.setFocus(item); + } }); }); } diff --git a/src/vs/workbench/browser/parts/views/viewsViewlet.ts b/src/vs/workbench/browser/parts/views/viewsViewlet.ts index 0f8768f3a59..e930b4be1fb 100644 --- a/src/vs/workbench/browser/parts/views/viewsViewlet.ts +++ b/src/vs/workbench/browser/parts/views/viewsViewlet.ts @@ -208,7 +208,9 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView } view = this.getView(id); view.setExpanded(true); - view.focus(); + if (focus) { + view.focus(); + } return TPromise.as(view); } diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index b0fc9f381ee..47d5a9cfadf 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -227,6 +227,8 @@ export interface ITreeViewer extends IDisposable { readonly onDidChangeSelection: Event; + readonly onDidChangeVisibility: Event; + refresh(treeItems?: ITreeItem[]): TPromise; setVisibility(visible: boolean): void; From edefeac3a5381433c23bf24d3b737dd25fcb791d Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 19 Jun 2018 11:36:27 +0200 Subject: [PATCH 014/228] Add doc --- src/vs/vscode.d.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 4b80158046c..95ea000bcb0 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -6171,6 +6171,9 @@ declare module 'vscode' { } + /** + * The event that is fired when there is a change in [tree view's visibility](#TreeView.visible) + */ export interface TreeViewVisibilityChangeEvent { /** From dae08eccaac470d2bf9164ee0fb79ee101c2a182 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 19 Jun 2018 11:38:12 +0200 Subject: [PATCH 015/228] update doc --- src/vs/vscode.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 95ea000bcb0..8de245c3763 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -6220,7 +6220,7 @@ declare module 'vscode' { /** * Reveals the given element in the tree view. - * If the tree view is not visible then the element is revealed after the tree view is shown. + * If the tree view is not visible then the tree view is shown and element is revealed. * * By default revealed element is selected and not focused. * In order to not to select, set the option `select` to `false`. From 3f86ddfada6d09109df16047dc42ec134b29f220 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 19 Jun 2018 23:10:30 +0200 Subject: [PATCH 016/228] Introduce double click command --- src/vs/vscode.d.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 8de245c3763..d8586f2bf7b 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -6304,10 +6304,15 @@ declare module 'vscode' { tooltip?: string | undefined; /** - * The [command](#Command) which should be run when the tree item is selected. + * The [command](#Command) that should run when the user selects the tree item. */ command?: Command; + /** + * The [command](#Command) that should run when the user double clicks on the tree item. + */ + doubleClickCommand?: Command; + /** * [TreeItemCollapsibleState](#TreeItemCollapsibleState) of the tree item. */ From accd32a120d44e72d34127ce15973cab2f99241b Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 20 Jun 2018 15:39:59 +0200 Subject: [PATCH 017/228] Code review feedback and remove double click command --- src/vs/vscode.d.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index d8586f2bf7b..6588af1c28c 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -6201,7 +6201,7 @@ declare module 'vscode' { /** * Currently selected elements. */ - readonly selection: ReadonlyArray; + readonly selection: T[]; /** * Event that is fired when the [selection](#TreeView.selection) has changed @@ -6304,15 +6304,10 @@ declare module 'vscode' { tooltip?: string | undefined; /** - * The [command](#Command) that should run when the user selects the tree item. + * The [command](#Command) that should be executed when the tree item is selected. */ command?: Command; - /** - * The [command](#Command) that should run when the user double clicks on the tree item. - */ - doubleClickCommand?: Command; - /** * [TreeItemCollapsibleState](#TreeItemCollapsibleState) of the tree item. */ From 4a23d5f5007a6ea553349e96be24df9f689f15ff Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 25 Jun 2018 12:37:41 +0200 Subject: [PATCH 018/228] Review comments and fix tests --- src/vs/vscode.d.ts | 6 +++--- src/vs/workbench/api/node/extHostTreeViews.ts | 8 ++++---- .../electron-browser/api/extHostTreeViews.test.ts | 14 +++++++------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 6588af1c28c..71329e06fea 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -6155,7 +6155,7 @@ declare module 'vscode' { /** * Element that is expanded or collapsed. */ - element: T; + readonly element: T; } @@ -6167,7 +6167,7 @@ declare module 'vscode' { /** * Selected elements. */ - selection: T[]; + readonly selection: T[]; } @@ -6179,7 +6179,7 @@ declare module 'vscode' { /** * `true` if the [tree view](#TreeView) is visible otherwise `false`. */ - visible: boolean; + readonly visible: boolean; } diff --git a/src/vs/workbench/api/node/extHostTreeViews.ts b/src/vs/workbench/api/node/extHostTreeViews.ts index 2df9b9076cd..19b4b163334 100644 --- a/src/vs/workbench/api/node/extHostTreeViews.ts +++ b/src/vs/workbench/api/node/extHostTreeViews.ts @@ -199,9 +199,9 @@ class ExtHostTreeView extends Disposable { const element = this.getExtensionElement(treeItemHandle); if (element) { if (expanded) { - this._onDidExpandElement.fire({ element }); + this._onDidExpandElement.fire(Object.freeze({ element })); } else { - this._onDidCollapseElement.fire({ element }); + this._onDidCollapseElement.fire(Object.freeze({ element })); } } } @@ -209,14 +209,14 @@ class ExtHostTreeView extends Disposable { setSelection(treeItemHandles: TreeItemHandle[]): void { if (!equals(this._selectedHandles, treeItemHandles)) { this._selectedHandles = treeItemHandles; - this._onDidChangeSelection.fire({ selection: this.selectedElements }); + this._onDidChangeSelection.fire(Object.freeze({ selection: this.selectedElements })); } } setVisible(visible: boolean): void { if (visible !== this._visible) { this._visible = visible; - this._onDidChangeVisibility.fire({ visible: this._visible }); + this._onDidChangeVisibility.fire(Object.freeze({ visible: this._visible })); } } diff --git a/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts b/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts index 284f16ac8ee..57894ee26cd 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts @@ -416,7 +416,7 @@ suite('ExtHostTreeView', function () { assert.deepEqual('treeDataProvider', revealTarget.args[0][0]); assert.deepEqual({ handle: '0/0:a', label: 'a', collapsibleState: TreeItemCollapsibleState.Collapsed }, removeUnsetKeys(revealTarget.args[0][1])); assert.deepEqual([], revealTarget.args[0][2]); - assert.equal(void 0, revealTarget.args[0][3]); + assert.deepEqual({ select: true, focus: false }, revealTarget.args[0][3]); }); }); @@ -429,7 +429,7 @@ suite('ExtHostTreeView', function () { assert.deepEqual('treeDataProvider', revealTarget.args[0][0]); assert.deepEqual({ handle: '0/0:a/0:aa', label: 'aa', collapsibleState: TreeItemCollapsibleState.None, parentHandle: '0/0:a' }, removeUnsetKeys(revealTarget.args[0][1])); assert.deepEqual([{ handle: '0/0:a', label: 'a', collapsibleState: TreeItemCollapsibleState.Collapsed }], (>revealTarget.args[0][2]).map(arg => removeUnsetKeys(arg))); - assert.equal(void 0, revealTarget.args[0][3]); + assert.deepEqual({ select: true, focus: false }, revealTarget.args[0][3]); }); }); @@ -444,7 +444,7 @@ suite('ExtHostTreeView', function () { assert.deepEqual('treeDataProvider', revealTarget.args[0][0]); assert.deepEqual({ handle: '0/0:a/0:aa', label: 'aa', collapsibleState: TreeItemCollapsibleState.None, parentHandle: '0/0:a' }, removeUnsetKeys(revealTarget.args[0][1])); assert.deepEqual([{ handle: '0/0:a', label: 'a', collapsibleState: TreeItemCollapsibleState.Collapsed }], (>revealTarget.args[0][2]).map(arg => removeUnsetKeys(arg))); - assert.equal(void 0, revealTarget.args[0][3]); + assert.deepEqual({ select: true, focus: false }, revealTarget.args[0][3]); })); }); @@ -458,7 +458,7 @@ suite('ExtHostTreeView', function () { }; const revealTarget = sinon.spy(target, '$reveal'); const treeView = testObject.createTreeView('treeDataProvider', { treeDataProvider: aCompleteNodeTreeDataProvider() }); - return treeView.reveal({ key: 'bac' }, { select: false }) + return treeView.reveal({ key: 'bac' }, { select: false, focus: false }) .then(() => { assert.ok(revealTarget.calledOnce); assert.deepEqual('treeDataProvider', revealTarget.args[0][0]); @@ -467,7 +467,7 @@ suite('ExtHostTreeView', function () { { handle: '0/0:b', label: 'b', collapsibleState: TreeItemCollapsibleState.Collapsed }, { handle: '0/0:b/0:ba', label: 'ba', collapsibleState: TreeItemCollapsibleState.Collapsed, parentHandle: '0/0:b' } ], (>revealTarget.args[0][2]).map(arg => removeUnsetKeys(arg))); - assert.deepEqual({ select: false }, revealTarget.args[0][3]); + assert.deepEqual({ select: false, focus: false }, revealTarget.args[0][3]); }); }); @@ -494,7 +494,7 @@ suite('ExtHostTreeView', function () { assert.deepEqual('treeDataProvider', revealTarget.args[0][0]); assert.deepEqual({ handle: '0/0:a/0:ac', label: 'ac', collapsibleState: TreeItemCollapsibleState.None, parentHandle: '0/0:a' }, removeUnsetKeys(revealTarget.args[0][1])); assert.deepEqual([{ handle: '0/0:a', label: 'a', collapsibleState: TreeItemCollapsibleState.Collapsed }], (>revealTarget.args[0][2]).map(arg => removeUnsetKeys(arg))); - assert.equal(void 0, revealTarget.args[0][3]); + assert.deepEqual({ select: true, focus: false }, revealTarget.args[0][3]); }); }); }); @@ -533,7 +533,7 @@ suite('ExtHostTreeView', function () { assert.deepEqual('treeDataProvider', revealTarget.args[0][0]); assert.deepEqual({ handle: '0/0:b/0:bc', label: 'bc', collapsibleState: TreeItemCollapsibleState.None, parentHandle: '0/0:b' }, removeUnsetKeys(revealTarget.args[0][1])); assert.deepEqual([{ handle: '0/0:b', label: 'b', collapsibleState: TreeItemCollapsibleState.Collapsed }], (>revealTarget.args[0][2]).map(arg => removeUnsetKeys(arg))); - assert.equal(void 0, revealTarget.args[0][3]); + assert.deepEqual({ select: true, focus: false }, revealTarget.args[0][3]); }); }); }); From 3b5bc1383736bc6320208795efb9e1d8cfb572ff Mon Sep 17 00:00:00 2001 From: Benjamin Crawford Date: Mon, 25 Jun 2018 12:41:05 +0100 Subject: [PATCH 019/228] max_old_space_size flag out of range for 32-bit builds. --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 486d28175db..9a84ea770b7 100644 --- a/package.json +++ b/package.json @@ -11,11 +11,11 @@ "test": "mocha", "preinstall": "node build/npm/preinstall.js", "postinstall": "node build/npm/postinstall.js", - "compile": "gulp compile --max_old_space_size=4096", - "watch": "gulp watch --max_old_space_size=4096", + "compile": "gulp compile --max_old_space_size=4095", + "watch": "gulp watch --max_old_space_size=4095", "monaco-editor-test": "mocha --only-monaco-editor", "precommit": "node build/gulpfile.hygiene.js", - "gulp": "gulp --max_old_space_size=4096", + "gulp": "gulp --max_old_space_size=4095", "7z": "7z", "update-grammars": "node build/npm/update-all-grammars.js", "update-localization-extension": "node build/npm/update-localization-extension.js", From b10cad5e0de47e35d15947cb4c246d00b650e82a Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 25 Jun 2018 13:50:24 +0200 Subject: [PATCH 020/228] fix #46266 --- .../editor/contrib/snippet/snippetSession.ts | 45 +++++++++++-------- .../snippet/test/snippetSession.test.ts | 9 +++- 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/src/vs/editor/contrib/snippet/snippetSession.ts b/src/vs/editor/contrib/snippet/snippetSession.ts index 9d9f336c929..1c183f104c5 100644 --- a/src/vs/editor/contrib/snippet/snippetSession.ts +++ b/src/vs/editor/contrib/snippet/snippetSession.ts @@ -9,7 +9,7 @@ import 'vs/css!./snippetSession'; import { getLeadingWhitespace } from 'vs/base/common/strings'; import { ITextModel, TrackedRangeStickiness, IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; import { EditOperation } from 'vs/editor/common/core/editOperation'; -import { TextmateSnippet, Placeholder, Choice, SnippetParser } from './snippetParser'; +import { TextmateSnippet, Placeholder, Choice, Text, SnippetParser } from './snippetParser'; import { Selection } from 'vs/editor/common/core/selection'; import { Range } from 'vs/editor/common/core/range'; import { IPosition } from 'vs/editor/common/core/position'; @@ -261,17 +261,26 @@ export class OneSnippet { export class SnippetSession { - static adjustWhitespace(model: ITextModel, position: IPosition, template: string): string { - + static adjustWhitespace2(model: ITextModel, position: IPosition, snippet: TextmateSnippet): void { const line = model.getLineContent(position.lineNumber); const lineLeadingWhitespace = getLeadingWhitespace(line, 0, position.column - 1); - const templateLines = template.split(/\r\n|\r|\n/); - for (let i = 1; i < templateLines.length; i++) { - let templateLeadingWhitespace = getLeadingWhitespace(templateLines[i]); - templateLines[i] = model.normalizeIndentation(lineLeadingWhitespace + templateLeadingWhitespace) + templateLines[i].substr(templateLeadingWhitespace.length); - } - return templateLines.join(model.getEOL()); + snippet.walk(marker => { + if (marker instanceof Text && !(marker.parent instanceof Choice)) { + // adjust indentation of text markers, except for choise elements + // which get adjusted when being selected + const lines = marker.value.split(/\r\n|\r|\n/); + for (let i = 1; i < lines.length; i++) { + let templateLeadingWhitespace = getLeadingWhitespace(lines[i]); + lines[i] = model.normalizeIndentation(lineLeadingWhitespace + templateLeadingWhitespace) + lines[i].substr(templateLeadingWhitespace.length); + } + const newValue = lines.join(model.getEOL()); + if (newValue !== marker.value) { + marker.parent.replace(marker, [new Text(newValue)]); + } + } + return true; + }); } static adjustSelection(model: ITextModel, selection: Selection, overwriteBefore: number, overwriteAfter: number): Selection { @@ -341,19 +350,19 @@ export class SnippetSession { .setStartPosition(extensionBefore.startLineNumber, extensionBefore.startColumn) .setEndPosition(extensionAfter.endLineNumber, extensionAfter.endColumn); + const snippet = new SnippetParser().parse(template, true, enforceFinalTabstop); + // adjust the template string to match the indentation and // whitespace rules of this insert location (can be different for each cursor) const start = snippetSelection.getStartPosition(); - const adjustedTemplate = SnippetSession.adjustWhitespace(model, start, template); + SnippetSession.adjustWhitespace2(model, start, snippet); - const snippet = new SnippetParser() - .parse(adjustedTemplate, true, enforceFinalTabstop) - .resolveVariables(new CompositeSnippetVariableResolver([ - modelBasedVariableResolver, - new ClipboardBasedVariableResolver(clipboardService, idx, indexedSelections.length), - new SelectionBasedVariableResolver(model, selection), - new TimeBasedVariableResolver - ])); + snippet.resolveVariables(new CompositeSnippetVariableResolver([ + modelBasedVariableResolver, + new ClipboardBasedVariableResolver(clipboardService, idx, indexedSelections.length), + new SelectionBasedVariableResolver(model, selection), + new TimeBasedVariableResolver + ])); const offset = model.getOffsetAt(start) + delta; delta += snippet.toString().length - model.getValueLengthInRange(snippetSelection); diff --git a/src/vs/editor/contrib/snippet/test/snippetSession.test.ts b/src/vs/editor/contrib/snippet/test/snippetSession.test.ts index 8093aa82221..95eaf41c1e8 100644 --- a/src/vs/editor/contrib/snippet/test/snippetSession.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetSession.test.ts @@ -12,6 +12,7 @@ import { SnippetSession } from 'vs/editor/contrib/snippet/snippetSession'; import { createTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { TextModel } from 'vs/editor/common/model/textModel'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { SnippetParser } from 'vs/editor/contrib/snippet/snippetParser'; suite('SnippetSession', function () { @@ -41,8 +42,9 @@ suite('SnippetSession', function () { test('normalize whitespace', function () { function assertNormalized(position: IPosition, input: string, expected: string): void { - const actual = SnippetSession.adjustWhitespace(model, position, input); - assert.equal(actual, expected); + const snippet = new SnippetParser().parse(input); + SnippetSession.adjustWhitespace2(model, position, snippet); + assert.equal(snippet.toTextmateString(), expected); } assertNormalized(new Position(1, 1), 'foo', 'foo'); @@ -51,6 +53,9 @@ suite('SnippetSession', function () { assertNormalized(new Position(2, 5), 'foo\r\tbar', 'foo\n bar'); assertNormalized(new Position(2, 3), 'foo\r\tbar', 'foo\n bar'); assertNormalized(new Position(2, 5), 'foo\r\tbar\nfoo', 'foo\n bar\n foo'); + + //Indentation issue with choice elements that span multiple lines #46266 + assertNormalized(new Position(2, 5), 'a\nb${1|foo,\nbar|}', 'a\n b${1|foo,\nbar|}'); }); test('adjust selection (overwrite[Before|After])', function () { From 559d2be8ab5572f8f6bd033a59cd6b0d7626fbca Mon Sep 17 00:00:00 2001 From: isidor Date: Mon, 25 Jun 2018 14:15:22 +0200 Subject: [PATCH 021/228] fixes #52748 --- .../parts/debug/browser/debugActionsWidget.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/parts/debug/browser/debugActionsWidget.ts b/src/vs/workbench/parts/debug/browser/debugActionsWidget.ts index b2812833309..a5e8f96b51d 100644 --- a/src/vs/workbench/parts/debug/browser/debugActionsWidget.ts +++ b/src/vs/workbench/parts/debug/browser/debugActionsWidget.ts @@ -159,7 +159,7 @@ export class DebugActionsWidget extends Themable implements IWorkbenchContributi // Prevent default to stop editor selecting text #8524 mouseMoveEvent.preventDefault(); // Reduce x by width of drag handle to reduce jarring #16604 - this.setCoordinates(mouseMoveEvent.posx - 14, mouseMoveEvent.posy); + this.setCoordinates(mouseMoveEvent.posx - 14, mouseMoveEvent.posy - this.partService.getTitleBarOffset()); }).once('mouseup', (e: MouseEvent) => { this.storePosition(); this.dragArea.removeClass('dragged'); @@ -167,8 +167,8 @@ export class DebugActionsWidget extends Themable implements IWorkbenchContributi }); }); - this.toUnbind.push(this.partService.onTitleBarVisibilityChange(() => this.positionDebugWidget())); - this.toUnbind.push(browser.onDidChangeZoomLevel(() => this.positionDebugWidget())); + this.toUnbind.push(this.partService.onTitleBarVisibilityChange(() => this.setYCoordinate())); + this.toUnbind.push(browser.onDidChangeZoomLevel(() => this.setYCoordinate())); } private storePosition(): void { @@ -199,10 +199,9 @@ export class DebugActionsWidget extends Themable implements IWorkbenchContributi } } - private positionDebugWidget(): void { + private setYCoordinate(y = 0): void { const titlebarOffset = this.partService.getTitleBarOffset(); - - $(this.$el).style('top', `${titlebarOffset}px`); + this.$el.style('top', `${titlebarOffset + y}px`); } private setCoordinates(x?: number, y?: number): void { @@ -223,7 +222,7 @@ export class DebugActionsWidget extends Themable implements IWorkbenchContributi } if ((y < HEIGHT / 2) || (y > HEIGHT + HEIGHT / 2)) { const moveToTop = y < HEIGHT; - this.$el.style('top', moveToTop ? '0px' : `${HEIGHT}px`); + this.setYCoordinate(moveToTop ? 0 : HEIGHT); this.storageService.store(DEBUG_ACTIONS_WIDGET_Y_KEY, moveToTop ? 0 : 2 * HEIGHT, StorageScope.GLOBAL); } } From b5da33167df5f2ed10244eae936e9feb54ed7589 Mon Sep 17 00:00:00 2001 From: isidor Date: Mon, 25 Jun 2018 14:47:13 +0200 Subject: [PATCH 022/228] fixes #52550 --- src/vs/workbench/electron-browser/workbench.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/electron-browser/workbench.ts b/src/vs/workbench/electron-browser/workbench.ts index 063ebe8e6b1..ac68914351a 100644 --- a/src/vs/workbench/electron-browser/workbench.ts +++ b/src/vs/workbench/electron-browser/workbench.ts @@ -712,7 +712,7 @@ export class Workbench extends Disposable implements IPartService { // Restore Zen Mode if active if (this.storageService.getBoolean(Workbench.zenModeActiveStorageKey, StorageScope.WORKSPACE, false)) { - this.toggleZenMode(true); + this.toggleZenMode(true, true); } // Restore Forced Editor Center Mode @@ -1215,7 +1215,7 @@ export class Workbench extends Disposable implements IPartService { return Identifiers.WORKBENCH_CONTAINER; } - toggleZenMode(skipLayout?: boolean): void { + toggleZenMode(skipLayout?: boolean, restoring = false): void { this.zenMode.active = !this.zenMode.active; this.zenMode.transitionDisposeables = dispose(this.zenMode.transitionDisposeables); @@ -1228,7 +1228,7 @@ export class Workbench extends Disposable implements IPartService { const config = this.configurationService.getValue('zenMode'); toggleFullScreen = !browser.isFullscreen() && config.fullScreen; - this.zenMode.transitionedToFullScreen = toggleFullScreen; + this.zenMode.transitionedToFullScreen = restoring ? config.fullScreen : toggleFullScreen; this.zenMode.transitionedToCenteredEditorLayout = !this.isEditorLayoutCentered() && config.centerLayout; this.zenMode.wasSideBarVisible = this.isVisible(Parts.SIDEBAR_PART); this.zenMode.wasPanelVisible = this.isVisible(Parts.PANEL_PART); From b038882cf7904093079555575baf8de28259c1ac Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 25 Jun 2018 15:16:48 +0200 Subject: [PATCH 023/228] fix #51667 --- src/vs/editor/contrib/snippet/snippetSession.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/vs/editor/contrib/snippet/snippetSession.ts b/src/vs/editor/contrib/snippet/snippetSession.ts index 1c183f104c5..7d5e74e4ad2 100644 --- a/src/vs/editor/contrib/snippet/snippetSession.ts +++ b/src/vs/editor/contrib/snippet/snippetSession.ts @@ -182,6 +182,14 @@ export class OneSnippet { const id = this._placeholderDecorations.get(placeholder); const range = this._editor.getModel().getDecorationRange(id); + if (!range) { + // one of the placeholder lost its decoration and + // therefore we bail out and pretend the placeholder + // (with its mirrors) doesn't exist anymore. + result.delete(placeholder.index); + break; + } + ranges.push(range); } } From f3115b8dd6a6aee2a0c000d3a7e034ede2ad34fd Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 25 Jun 2018 15:22:43 +0200 Subject: [PATCH 024/228] build stuff --- build/gulpfile.vscode.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 47f243676b5..bf2baa6185e 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -508,6 +508,10 @@ gulp.task('generate-vscode-configuration', () => { return reject(new Error('$AGENT_BUILDDIRECTORY not set')); } + if (process.env.VSCODE_QUALITY !== 'insider' && process.env.VSCODE_QUALITY !== 'stable') { + return resolve(); + } + const userDataDir = path.join(os.tmpdir(), 'tmpuserdata'); const extensionsDir = path.join(os.tmpdir(), 'tmpextdir'); const appName = process.env.VSCODE_QUALITY === 'insider' ? 'Visual\\ Studio\\ Code\\ -\\ Insiders.app' : 'Visual\\ Studio\\ Code.app'; From 72ecb31c7293cc759e78473ce0d617cac34fcd18 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 25 Jun 2018 15:26:46 +0200 Subject: [PATCH 025/228] fix #47553 --- .../referenceSearch/referencesController.ts | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/vs/editor/contrib/referenceSearch/referencesController.ts b/src/vs/editor/contrib/referenceSearch/referencesController.ts index 4f0182c8d6c..2c03e0d6ebc 100644 --- a/src/vs/editor/contrib/referenceSearch/referencesController.ts +++ b/src/vs/editor/contrib/referenceSearch/referencesController.ts @@ -73,10 +73,12 @@ export abstract class ReferencesController implements editorCommon.IEditorContri } public dispose(): void { - if (this._widget) { - this._widget.dispose(); - this._widget = null; - } + this._referenceSearchVisible.reset(); + dispose(this._disposables); + dispose(this._widget); + dispose(this._model); + this._widget = null; + this._model = null; this._editor = null; } @@ -189,16 +191,12 @@ export abstract class ReferencesController implements editorCommon.IEditorContri } public closeWidget(): void { - if (this._widget) { - this._widget.dispose(); - this._widget = null; - } + dispose(this._widget); + this._widget = null; this._referenceSearchVisible.reset(); this._disposables = dispose(this._disposables); - if (this._model) { - this._model.dispose(); - this._model = null; - } + dispose(this._model); + this._model = null; this._editor.focus(); this._requestIdPool += 1; // Cancel pending requests } From 0ecbfa0fc185f6e6f98ba4c5fe2c5de3918b0060 Mon Sep 17 00:00:00 2001 From: isidor Date: Mon, 25 Jun 2018 16:27:07 +0200 Subject: [PATCH 026/228] explorer decoration provider refactor fixes #50866 --- .../parts/files/common/explorerModel.ts | 17 ++++++++++------- .../views/explorerDecorationsProvider.ts | 18 +++++++++++++++--- .../electron-browser/views/explorerView.ts | 14 ++++++++++---- 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/parts/files/common/explorerModel.ts b/src/vs/workbench/parts/files/common/explorerModel.ts index 4b72173d75a..bdb52e71e26 100644 --- a/src/vs/workbench/parts/files/common/explorerModel.ts +++ b/src/vs/workbench/parts/files/common/explorerModel.ts @@ -74,11 +74,12 @@ export class ExplorerItem { private _isSymbolicLink: boolean; private _isReadonly: boolean; private children: Map; + private _isError: boolean; public parent: ExplorerItem; public isDirectoryResolved: boolean; - constructor(resource: URI, public root: ExplorerItem, isSymbolicLink?: boolean, isReadonly?: boolean, isDirectory?: boolean, name: string = resources.basenameOrAuthority(resource), mtime?: number, etag?: string) { + constructor(resource: URI, public root: ExplorerItem, isSymbolicLink?: boolean, isReadonly?: boolean, isDirectory?: boolean, name: string = resources.basenameOrAuthority(resource), mtime?: number, etag?: string, isError?: boolean) { this.resource = resource; this._name = name; this.isDirectory = !!isDirectory; @@ -86,6 +87,7 @@ export class ExplorerItem { this._isReadonly = !!isReadonly; this.etag = etag; this.mtime = mtime; + this._isError = !!isError; if (!this.root) { this.root = this; @@ -106,6 +108,10 @@ export class ExplorerItem { return this._isReadonly; } + public get isError(): boolean { + return this._isError; + } + public set isDirectory(value: boolean) { if (value !== this._isDirectory) { this._isDirectory = value; @@ -118,10 +124,6 @@ export class ExplorerItem { } - public get nonexistentRoot(): boolean { - return this.isRoot && !this.isDirectoryResolved && this.isDirectory; - } - public get name(): string { return this._name; } @@ -145,8 +147,8 @@ export class ExplorerItem { return this === this.root; } - public static create(raw: IFileStat, root: ExplorerItem, resolveTo?: URI[]): ExplorerItem { - const stat = new ExplorerItem(raw.resource, root, raw.isSymbolicLink, raw.isReadonly, raw.isDirectory, raw.name, raw.mtime, raw.etag); + public static create(raw: IFileStat, root: ExplorerItem, resolveTo?: URI[], isError = false): ExplorerItem { + const stat = new ExplorerItem(raw.resource, root, raw.isSymbolicLink, raw.isReadonly, raw.isDirectory, raw.name, raw.mtime, raw.etag, isError); // Recursively add children if present if (stat.isDirectory) { @@ -195,6 +197,7 @@ export class ExplorerItem { local.isDirectoryResolved = disk.isDirectoryResolved; local._isSymbolicLink = disk.isSymbolicLink; local._isReadonly = disk.isReadonly; + local._isError = disk.isError; // Merge Children if resolved if (mergingDirectories && disk.isDirectoryResolved) { diff --git a/src/vs/workbench/parts/files/electron-browser/views/explorerDecorationsProvider.ts b/src/vs/workbench/parts/files/electron-browser/views/explorerDecorationsProvider.ts index dcbb387a155..9740c47831b 100644 --- a/src/vs/workbench/parts/files/electron-browser/views/explorerDecorationsProvider.ts +++ b/src/vs/workbench/parts/files/electron-browser/views/explorerDecorationsProvider.ts @@ -12,27 +12,35 @@ import { Model } from 'vs/workbench/parts/files/common/explorerModel'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IDecorationsProvider, IDecorationData } from 'vs/workbench/services/decorations/browser/decorations'; import { listInvalidItemForeground } from 'vs/platform/theme/common/colorRegistry'; +import { IDisposable } from 'vscode-xterm'; +import { dispose } from 'vs/base/common/lifecycle'; export class ExplorerDecorationsProvider implements IDecorationsProvider { readonly label: string = localize('label', "Explorer"); private _onDidChange = new Emitter(); + private toDispose: IDisposable[]; constructor( private model: Model, @IWorkspaceContextService contextService: IWorkspaceContextService ) { - contextService.onDidChangeWorkspaceFolders(e => { + this.toDispose = []; + this.toDispose.push(contextService.onDidChangeWorkspaceFolders(e => { this._onDidChange.fire(e.changed.concat(e.added).map(wf => wf.uri)); - }); + })); } get onDidChange(): Event { return this._onDidChange.event; } + changed(uris: URI[]): void { + this._onDidChange.fire(uris); + } + provideDecorations(resource: URI): IDecorationData { const fileStat = this.model.findClosest(resource); - if (fileStat && fileStat.nonexistentRoot) { + if (fileStat && fileStat.isRoot && fileStat.isError) { return { tooltip: localize('canNotResolve', "Can not resolve workspace folder"), letter: '!', @@ -48,4 +56,8 @@ export class ExplorerDecorationsProvider implements IDecorationsProvider { return undefined; } + + dispose(): IDisposable[] { + return dispose(this.toDispose); + } } diff --git a/src/vs/workbench/parts/files/electron-browser/views/explorerView.ts b/src/vs/workbench/parts/files/electron-browser/views/explorerView.ts index b471abb1017..cadc88c5131 100644 --- a/src/vs/workbench/parts/files/electron-browser/views/explorerView.ts +++ b/src/vs/workbench/parts/files/electron-browser/views/explorerView.ts @@ -78,6 +78,7 @@ export class ExplorerView extends TreeViewsViewletPanel implements IExplorerView private settings: object; private treeContainer: HTMLElement; private dragHandler: DelayedDragHandler; + private decorationProvider: ExplorerDecorationsProvider; private isDisposed: boolean; constructor( @@ -114,7 +115,9 @@ export class ExplorerView extends TreeViewsViewletPanel implements IExplorerView (event: IConfigurationChangeEvent) => event.affectsConfiguration(FILES_EXCLUDE_CONFIG) ); - decorationService.registerDecorationsProvider(new ExplorerDecorationsProvider(this.model, contextService)); + this.decorationProvider = new ExplorerDecorationsProvider(this.model, contextService); + decorationService.registerDecorationsProvider(this.decorationProvider); + this.disposables.push(this.decorationProvider); } private getFileEventsExcludes(root?: URI): glob.IExpression { @@ -782,7 +785,10 @@ export class ExplorerView extends TreeViewsViewletPanel implements IExplorerView }); } - const promise = this.resolveRoots(targetsToResolve, targetsToExpand); + const promise = this.resolveRoots(targetsToResolve, targetsToExpand).then(result => { + this.decorationProvider.changed(targetsToResolve.map(t => t.root.resource)); + return result; + }); this.progressService.showWhile(promise, this.partService.isCreated() ? 800 : 3200 /* less ugly initial startup */); return promise; @@ -798,7 +804,7 @@ export class ExplorerView extends TreeViewsViewletPanel implements IExplorerView mtime: 0, etag: undefined, isDirectory: true - }, root); + }, root, undefined, true); const setInputAndExpand = (input: ExplorerItem | Model, statsToExpand: ExplorerItem[]) => { // Make sure to expand all folders that where expanded in the previous session @@ -842,7 +848,7 @@ export class ExplorerView extends TreeViewsViewletPanel implements IExplorerView let delayer = new Delayer(100); let delayerPromise: TPromise; return TPromise.join(targetsToResolve.map((target, index) => this.fileService.resolveFile(target.resource, target.options) - .then(result => result.isDirectory ? ExplorerItem.create(result, target.root, target.options.resolveTo) : errorFileStat(target.resource, target.root), err => errorFileStat(target.resource, target.root)) + .then(result => result.isDirectory ? ExplorerItem.create(result, target.root, target.options.resolveTo) : errorFileStat(target.resource, target.root), () => errorFileStat(target.resource, target.root)) .then(modelStat => { // Subsequent refresh: Merge stat into our local model and refresh tree if (index < this.model.roots.length) { From 63e86f4153f36b8b1c19e5da38f7ed6170d9e8c4 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 25 Jun 2018 16:20:51 +0200 Subject: [PATCH 027/228] remove logging entries --- src/vs/base/node/zip.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/base/node/zip.ts b/src/vs/base/node/zip.ts index bb3c6daf9d1..652fb21c73b 100644 --- a/src/vs/base/node/zip.ts +++ b/src/vs/base/node/zip.ts @@ -114,7 +114,6 @@ function extractZip(zipfile: ZipFile, targetPath: string, options: IOptions, log }, e)); zipfile.readEntry(); zipfile.on('entry', (entry: Entry) => { - logService.debug(targetPath, 'Found', entry.fileName); if (isCanceled) { return; From 4d93f9cc376b4fcd9936d2bb9b7bc45964953374 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 25 Jun 2018 16:38:21 +0200 Subject: [PATCH 028/228] Fix #51145 --- src/vs/code/node/cliProcessMain.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index af389ec5dc2..ed3244d1794 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -38,6 +38,7 @@ import { ILogService, getLogLevel } from 'vs/platform/log/common/log'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { CommandLineDialogService } from 'vs/platform/dialogs/node/dialogService'; +import { areSameExtensions, getGalleryExtensionIdFromLocal } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; const notFound = (id: string) => localize('notFound', "Extension '{0}' not found.", id); const notInstalled = (id: string) => localize('notInstalled', "Extension '{0}' is not installed.", id); @@ -175,7 +176,7 @@ class Main { return sequence(extensions.map(extension => () => { return getExtensionId(extension).then(id => { return this.extensionManagementService.getInstalled(LocalExtensionType.User).then(installed => { - const [extension] = installed.filter(e => getId(e.manifest) === id); + const [extension] = installed.filter(e => areSameExtensions({ id: getGalleryExtensionIdFromLocal(e) }, { id })); if (!extension) { return TPromise.wrapError(new Error(`${notInstalled(id)}\n${useId}`)); From c273b7b768a565528ed3c637155b58a5788fb637 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 25 Jun 2018 16:43:42 +0200 Subject: [PATCH 029/228] outline - don't unset selection when not finding an item at the editor position --- src/vs/workbench/parts/outline/electron-browser/outlinePanel.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/vs/workbench/parts/outline/electron-browser/outlinePanel.ts b/src/vs/workbench/parts/outline/electron-browser/outlinePanel.ts index 9260046c508..82f3ebbc1d2 100644 --- a/src/vs/workbench/parts/outline/electron-browser/outlinePanel.ts +++ b/src/vs/workbench/parts/outline/electron-browser/outlinePanel.ts @@ -647,8 +647,6 @@ export class OutlinePanel extends ViewletPanel { await this._tree.reveal(item, .5); this._tree.setFocus(item, this); this._tree.setSelection([item], this); - } else { - this._tree.setSelection([], this); } } From 5befa5a362ac6a1c3f8e7caf6dab434a0955f868 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 25 Jun 2018 16:49:02 +0200 Subject: [PATCH 030/228] Add an option to configure hover stickiness (#15667) --- src/vs/editor/common/config/commonEditorConfig.ts | 5 +++++ src/vs/editor/common/config/editorOptions.ts | 13 +++++++++++-- src/vs/editor/contrib/hover/hover.ts | 10 +++++++--- src/vs/monaco.d.ts | 6 ++++++ 4 files changed, 29 insertions(+), 5 deletions(-) diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index 7a327a52752..d38d7731581 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -352,6 +352,11 @@ const editorConfiguration: IConfigurationNode = { 'default': EDITOR_DEFAULTS.contribInfo.hover.delay, 'description': nls.localize('hover.delay', "Controls the delay after which to show the hover") }, + 'editor.hover.sticky': { + 'type': 'boolean', + 'default': EDITOR_DEFAULTS.contribInfo.hover.sticky, + 'description': nls.localize('hover.sticky', "Controls if the hover should remain visible when mouse is moved over it") + }, 'editor.find.seedSearchStringFromSelection': { 'type': 'boolean', 'default': EDITOR_DEFAULTS.contribInfo.find.seedSearchStringFromSelection, diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 7e20281925b..36aaa892ac0 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -151,6 +151,11 @@ export interface IEditorHoverOptions { * Defaults to 300. */ delay?: number; + /** + * Is the hover sticky such that it can be clicked and its contents selected? + * Defaults to true. + */ + sticky?: boolean; } /** @@ -822,6 +827,7 @@ export interface InternalEditorFindOptions { export interface InternalEditorHoverOptions { readonly enabled: boolean; readonly delay: number; + readonly sticky: boolean; } export interface EditorWrappingInfo { @@ -1216,6 +1222,7 @@ export class InternalEditorOptions { return ( a.enabled === b.enabled && a.delay === b.delay + && a.sticky === b.sticky ); } @@ -1702,7 +1709,8 @@ export class EditorOptionsValidator { return { enabled: _boolean(opts.enabled, defaults.enabled), - delay: _clampedInt(opts.delay, defaults.delay, 0, 10000) + delay: _clampedInt(opts.delay, defaults.delay, 0, 10000), + sticky: _boolean(opts.sticky, defaults.sticky) }; } @@ -2401,7 +2409,8 @@ export const EDITOR_DEFAULTS: IValidatedEditorOptions = { selectionClipboard: true, hover: { enabled: true, - delay: 300 + delay: 300, + sticky: true }, links: true, contextmenu: true, diff --git a/src/vs/editor/contrib/hover/hover.ts b/src/vs/editor/contrib/hover/hover.ts index 6296a0e50a4..45f39711585 100644 --- a/src/vs/editor/contrib/hover/hover.ts +++ b/src/vs/editor/contrib/hover/hover.ts @@ -54,6 +54,7 @@ export class ModesHoverController implements IEditorContribution { private _isMouseDown: boolean; private _hoverClicked: boolean; private _isHoverEnabled: boolean; + private _isHoverSticky: boolean; static get(editor: ICodeEditor): ModesHoverController { return editor.getContribution(ModesHoverController.ID); @@ -83,7 +84,9 @@ export class ModesHoverController implements IEditorContribution { private _hookEvents(): void { const hideWidgetsEventHandler = () => this._hideWidgets(); - this._isHoverEnabled = this._editor.getConfiguration().contribInfo.hover.enabled; + const hoverOpts = this._editor.getConfiguration().contribInfo.hover; + this._isHoverEnabled = hoverOpts.enabled; + this._isHoverSticky = hoverOpts.sticky; if (this._isHoverEnabled) { this._toUnhook.push(this._editor.onMouseDown((e: IEditorMouseEvent) => this._onEditorMouseDown(e))); this._toUnhook.push(this._editor.onMouseUp((e: IEditorMouseEvent) => this._onEditorMouseUp(e))); @@ -142,6 +145,7 @@ export class ModesHoverController implements IEditorContribution { } private _onEditorMouseMove(mouseEvent: IEditorMouseEvent): void { + // const this._editor.getConfiguration().contribInfo.hover.sticky; let targetType = mouseEvent.target.type; const hasStopKey = (platform.isMacintosh ? mouseEvent.event.metaKey : mouseEvent.event.ctrlKey); @@ -149,12 +153,12 @@ export class ModesHoverController implements IEditorContribution { return; } - if (targetType === MouseTargetType.CONTENT_WIDGET && mouseEvent.target.detail === ModesContentHoverWidget.ID && !hasStopKey) { + if (this._isHoverSticky && targetType === MouseTargetType.CONTENT_WIDGET && mouseEvent.target.detail === ModesContentHoverWidget.ID && !hasStopKey) { // mouse moved on top of content hover widget return; } - if (targetType === MouseTargetType.OVERLAY_WIDGET && mouseEvent.target.detail === ModesGlyphHoverWidget.ID && !hasStopKey) { + if (this._isHoverSticky && targetType === MouseTargetType.OVERLAY_WIDGET && mouseEvent.target.detail === ModesGlyphHoverWidget.ID && !hasStopKey) { // mouse moved on top of overlay hover widget return; } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 2db548df611..7dcf6436363 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -2502,6 +2502,11 @@ declare namespace monaco.editor { * Defaults to 300. */ delay?: number; + /** + * Is the hover sticky such that it can be clicked and its contents selected? + * Defaults to true. + */ + sticky?: boolean; } /** @@ -3102,6 +3107,7 @@ declare namespace monaco.editor { export interface InternalEditorHoverOptions { readonly enabled: boolean; readonly delay: number; + readonly sticky: boolean; } export interface EditorWrappingInfo { From db638418440a4f043512e04467a6d1deb3fb925c Mon Sep 17 00:00:00 2001 From: Andre Weinand Date: Mon, 25 Jun 2018 17:15:02 +0200 Subject: [PATCH 031/228] fix #51673 --- src/vs/workbench/parts/debug/node/debugAdapter.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/workbench/parts/debug/node/debugAdapter.ts b/src/vs/workbench/parts/debug/node/debugAdapter.ts index 0e59f09acdd..a322f0d8779 100644 --- a/src/vs/workbench/parts/debug/node/debugAdapter.ts +++ b/src/vs/workbench/parts/debug/node/debugAdapter.ts @@ -321,6 +321,10 @@ export class DebugAdapter extends StreamDebugAdapter { stopSession(): TPromise { + if (!this.serverProcess) { + return TPromise.as(null); + } + // when killing a process in windows its child // processes are *not* killed but become root // processes. Therefore we use TASKKILL.EXE From ba6aa48b6e73761be64569a8b91220a16880e7c5 Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Mon, 25 Jun 2018 17:30:51 +0200 Subject: [PATCH 032/228] Documentation (#49340) --- src/vs/vscode.proposed.d.ts | 185 ++++++++++++++++++ .../browser/parts/quickinput/quickInput.ts | 4 +- 2 files changed, 187 insertions(+), 2 deletions(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 99ece63c7de..e342923c195 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -523,90 +523,275 @@ declare module 'vscode' { export namespace window { + /** + * A back button for [QuickPick](#QuickPick) and [InputBox](#InputBox). + * + * When a navigation 'back' button is needed this one should be used for consistency. + * It comes with a predefined icon, tooltip and location. + */ export const quickInputBackButton: QuickInputButton; + /** + * Creates a [QuickPick](#QuickPick) to let the user pick an item from a list + * of items of type T. + * + * Note that in many cases the more convenient [window.showQuickPick](#window.showQuickPick) + * is easier to use. + * + * @return A new [QuickPick](#QuickPick). + */ export function createQuickPick(): QuickPick; + /** + * Creates a [InputBox](#InputBox) to let the user enter some text input. + * + * Note that in many cases the more convenient [window.showInputBox](#window.showInputBox) + * is easier to use. + * + * @return A new [InputBox](#InputBox). + */ export function createInputBox(): InputBox; } + /** + * A light-weight user input UI that is intially not visible. After + * configuring it through its properties the extension can make it + * visible by calling [QuickInput.show](#QuickInput.show). + * + * There are several reasons why this UI might have to be hidden and + * the extension will be notified through [QuickInput.onDidHide](#QuickInput.onDidHide). + * (Examples include: an explict call to [QuickInput.hide](#QuickInput.hide), + * the user pressing Esc, some other input UI opening, etc.) + * + * A user pressing Enter or some other gesture implying acceptance + * of the current state does not automatically hide this UI component. + * It is up to the extension to decide whether to accept the user's input + * and if the UI should indeed be hidden through a call to [QuickInput.hide](#QuickInput.hide). + * + * When the extension no longer needs this input UI, it should + * [QuickInput.dispose](#QuickInput.dispose) it to allow for freeing up + * any resources associated with it. + * + * See [QuickPick](#QuickPick) and [InputBox](#InputBox) for concrete UIs. + */ export interface QuickInput { + /** + * An optional title. + */ title: string | undefined; + /** + * An optional current step count. + */ step: number | undefined; + /** + * An optional total step count. + */ totalSteps: number | undefined; + /** + * If the UI should allow for user input. Defaults to true. + * + * Change this to false, e.g., while validating user input or + * loading data for the next step in user input. + */ enabled: boolean; + /** + * If the UI should show a progress indicator. Defaults to false. + * + * Change this to true, e.g., while loading more data or validating + * user input. + */ busy: boolean; + /** + * If the UI should stay open even when loosing UI focus. Defaults to false. + */ ignoreFocusOut: boolean; + /** + * Makes the input UI visible in its current configuration. Any other input + * UI will first fire an [QuickInput.onDidHide](#QuickInput.onDidHide) event. + */ show(): void; + /** + * Hides this input UI. This will also fire an [QuickInput.onDidHide](#QuickInput.onDidHide) + * event. + */ hide(): void; + /** + * An event signaling when this input UI is hidden. + * + * There are several reasons why this UI might have to be hidden and + * the extension will be notified through [QuickInput.onDidHide](#QuickInput.onDidHide). + * (Examples include: an explict call to [QuickInput.hide](#QuickInput.hide), + * the user pressing Esc, some other input UI opening, etc.) + */ onDidHide: Event; + /** + * Dispose of this input UI and any associated resources. If it is still + * visible, it is first hidden. After this call the input UI is no longer + * functional and no additional methods or properties on it should be + * accessed. Instead a new input UI should be created. + */ dispose(): void; } + /** + * A concrete [QuickInput](#QuickInput) to let the user pick an item from a + * list of items of type T. The items can be filtered through a filter text field and + * there is an option [canSelectMany](#QuickPick.canSelectMany) to allow for + * selecting multiple items. + * + * Note that in many cases the more convenient [window.showQuickPick](#window.showQuickPick) + * is easier to use. + */ export interface QuickPick extends QuickInput { + /** + * Current value of the filter text. + */ value: string; + /** + * Optional placeholder in the filter text. + */ placeholder: string | undefined; + /** + * An event signaling when the value of the filter text has changed. + */ readonly onDidChangeValue: Event; + /** + * An event signaling when the user indicated acceptance of the selected item(s). + */ readonly onDidAccept: Event; + /** + * Buttons for actions in the UI. + */ buttons: ReadonlyArray; + /** + * An event signaling when a button was triggered. + */ readonly onDidTriggerButton: Event; + /** + * Items to pick from. + */ items: ReadonlyArray; + /** + * If multiple items can be selected at the same time. Defaults to false. + */ canSelectMany: boolean; + /** + * If the filter text should also be matched against the description of the items. Defaults to false. + */ matchOnDescription: boolean; + /** + * If the filter text should also be matched against the detail of the items. Defaults to false. + */ matchOnDetail: boolean; + /** + * Active items. This can be read and updated by the extension. + */ activeItems: ReadonlyArray; + /** + * An event signaling when the active items have changed. + */ readonly onDidChangeActive: Event; + /** + * Selected items. This can be read and updated by the extension. + */ selectedItems: ReadonlyArray; + /** + * An event signaling when the selected items have changed. + */ readonly onDidChangeSelection: Event; } + /** + * A concrete [QuickInput](#QuickInput) to let the user input a text value. + * + * Note that in many cases the more convenient [window.showInputBox](#window.showInputBox) + * is easier to use. + */ export interface InputBox extends QuickInput { + /** + * Current input value. + */ value: string; + /** + * Optional placeholder in the filter text. + */ placeholder: string | undefined; + /** + * If the input value should be hidden. Defaults to false. + */ password: boolean; + /** + * An event signaling when the value has changed. + */ readonly onDidChangeValue: Event; + /** + * An event signaling when the user indicated acceptance of the input value. + */ readonly onDidAccept: Event; + /** + * Buttons for actions in the UI. + */ buttons: ReadonlyArray; + /** + * An event signaling when a button was triggered. + */ readonly onDidTriggerButton: Event; + /** + * An optional prompt text providing some ask or explanation to the user. + */ prompt: string | undefined; + /** + * An optional validation message indicating a problem with the current input value. + */ validationMessage: string | undefined; } + /** + * Button for an action in a [QuickPick](#QuickPick) or [InputBox](#InputBox). + */ export interface QuickInputButton { + + /** + * Icon for the button. + */ readonly iconPath: string | Uri | { light: string | Uri; dark: string | Uri } | ThemeIcon; + + /** + * An optional tooltip. + */ readonly tooltip?: string | undefined; } diff --git a/src/vs/workbench/browser/parts/quickinput/quickInput.ts b/src/vs/workbench/browser/parts/quickinput/quickInput.ts index 3895d568ae1..23c7d23e53e 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInput.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInput.ts @@ -285,8 +285,8 @@ class QuickPick extends QuickInput implements IQuickPi private _items: T[] = []; private itemsUpdated = false; private _canSelectMany = false; - private _matchOnDescription = true; - private _matchOnDetail = true; + private _matchOnDescription = false; + private _matchOnDetail = false; private _activeItems: T[] = []; private activeItemsUpdated = false; private onDidChangeActiveEmitter = new Emitter(); From 3669afe7e966b9d8927b2c20ba73131af470cf6b Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 25 Jun 2018 18:20:12 +0200 Subject: [PATCH 033/228] Fix #48656 --- .../sharedProcess/sharedProcessMain.ts | 8 +++- .../node/extensionManagementService.ts | 38 ++++++++++++++----- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 58473214eeb..50027d9528e 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -42,6 +42,7 @@ import { ILocalizationsService } from 'vs/platform/localizations/common/localiza import { LocalizationsChannel } from 'vs/platform/localizations/common/localizationsIpc'; import { DialogChannelClient } from 'vs/platform/dialogs/common/dialogIpc'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; export interface ISharedProcessConfiguration { readonly machineId: string; @@ -61,11 +62,13 @@ const eventPrefix = 'monacoworkbench'; function main(server: Server, initData: ISharedProcessInitData, configuration: ISharedProcessConfiguration): void { const services = new ServiceCollection(); + const disposables: IDisposable[] = []; + process.once('exit', () => dispose(disposables)); const environmentService = new EnvironmentService(initData.args, process.execPath); const logLevelClient = new LogLevelSetterChannelClient(server.getChannel('loglevel', { route: () => 'main' })); const logService = new FollowerLogService(logLevelClient, createSpdLogService('sharedprocess', initData.logLevel, environmentService.logsPath)); - process.once('exit', () => logService.dispose()); + disposables.push(logService); logService.info('main', JSON.stringify(configuration)); @@ -98,7 +101,7 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I // It is important to dispose the AI adapter properly because // only then they flush remaining data. - process.once('exit', () => appenders.forEach(a => a.dispose())); + disposables.push(...appenders); const appender = combinedAppender(...appenders); server.registerChannel('telemetryAppender', new TelemetryAppenderChannel(appender)); @@ -138,6 +141,7 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I server.registerChannel('localizations', localizationsChannel); createSharedProcessContributions(instantiationService2); + disposables.push(extensionManagementService as ExtensionManagementService); }); }); } diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 611209b6926..8a57dd69022 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -114,6 +114,7 @@ export class ExtensionManagementService extends Disposable implements IExtension private lastReportTimestamp = 0; private readonly installationStartTime: Map = new Map(); private readonly installingExtensions: Map> = new Map>(); + private readonly uninstallingExtensions: Map> = new Map>(); private readonly manifestCache: ExtensionsManifestCache; private readonly extensionLifecycle: ExtensionsLifecycle; @@ -140,9 +141,15 @@ export class ExtensionManagementService extends Disposable implements IExtension this.extensionsPath = environmentService.extensionsPath; this.uninstalledPath = path.join(this.extensionsPath, '.obsolete'); this.uninstalledFileLimiter = new Limiter(1); - this._register(toDisposable(() => this.installingExtensions.clear())); this.manifestCache = this._register(new ExtensionsManifestCache(environmentService, this)); this.extensionLifecycle = this._register(new ExtensionsLifecycle(this.logService)); + + this._register(toDisposable(() => { + this.installingExtensions.forEach(promise => promise.cancel()); + this.uninstallingExtensions.forEach(promise => promise.cancel()); + this.installingExtensions.clear(); + this.uninstallingExtensions.clear(); + })); } install(zipPath: string): TPromise { @@ -208,7 +215,7 @@ export class ExtensionManagementService extends Disposable implements IExtension } private installFromZipPath(identifier: IExtensionIdentifier, zipPath: string, metadata: IGalleryMetadata, manifest: IExtensionManifest): TPromise { - return this.getInstalled() + return this.toNonCancellablePromise(this.getInstalled() .then(installed => { const operation = this.getOperation({ id: getIdFromLocalExtensionId(identifier.id), uuid: identifier.uuid }, installed); return this.installExtension({ zipPath, id: identifier.id, metadata }) @@ -230,12 +237,12 @@ export class ExtensionManagementService extends Disposable implements IExtension local => { this._onDidInstallExtension.fire({ identifier, zipPath, local, operation }); return local; }, error => { this._onDidInstallExtension.fire({ identifier, zipPath, operation, error }); return TPromise.wrapError(error); } ); - }); + })); } installFromGallery(extension: IGalleryExtension): TPromise { this.onInstallExtensions([extension]); - return this.getInstalled(LocalExtensionType.User) + return this.toNonCancellablePromise(this.getInstalled(LocalExtensionType.User) .then(installed => this.collectExtensionsToInstall(extension) .then( extensionsToInstall => { @@ -249,7 +256,7 @@ export class ExtensionManagementService extends Disposable implements IExtension .then(() => locals.filter(l => areSameExtensions({ id: getGalleryExtensionIdFromLocal(l), uuid: l.identifier.uuid }, extension.identifier))[0]), errors => this.onDidInstallExtensions(extensionsToInstall, [], operataions, errors)); }, - error => this.onDidInstallExtensions([extension], [], [this.getOperation(extension.identifier, installed)], [error]))); + error => this.onDidInstallExtensions([extension], [], [this.getOperation(extension.identifier, installed)], [error])))); } reinstallFromGallery(extension: ILocalExtension): TPromise { @@ -487,13 +494,13 @@ export class ExtensionManagementService extends Disposable implements IExtension } uninstall(extension: ILocalExtension, force = false): TPromise { - return this.getInstalled(LocalExtensionType.User) + return this.toNonCancellablePromise(this.getInstalled(LocalExtensionType.User) .then(installed => { const promises = installed .filter(e => e.manifest.publisher === extension.manifest.publisher && e.manifest.name === extension.manifest.name) .map(e => this.checkForDependenciesAndUninstall(e, installed, force)); return TPromise.join(promises).then(() => null, error => TPromise.wrapError(this.joinErrors(error))); - }); + })); } updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): TPromise { @@ -667,9 +674,16 @@ export class ExtensionManagementService extends Disposable implements IExtension } private uninstallExtension(local: ILocalExtension): TPromise { - // Set all versions of the extension as uninstalled - return this.scanUserExtensions(false) - .then(userExtensions => this.setUninstalled(...userExtensions.filter(u => areSameExtensions({ id: getGalleryExtensionIdFromLocal(u), uuid: u.identifier.uuid }, { id: getGalleryExtensionIdFromLocal(local), uuid: local.identifier.uuid })))); + const id = getGalleryExtensionIdFromLocal(local); + let promise = this.uninstallingExtensions.get(id); + if (!promise) { + // Set all versions of the extension as uninstalled + promise = this.scanUserExtensions(false) + .then(userExtensions => this.setUninstalled(...userExtensions.filter(u => areSameExtensions({ id: getGalleryExtensionIdFromLocal(u), uuid: u.identifier.uuid }, { id, uuid: local.identifier.uuid })))) + .then(() => { this.uninstallingExtensions.delete(id); }); + this.uninstallingExtensions.set(id, promise); + } + return promise; } private async postUninstallExtension(extension: ILocalExtension, error?: Error): TPromise { @@ -863,6 +877,10 @@ export class ExtensionManagementService extends Disposable implements IExtension }); } + private toNonCancellablePromise(promise: TPromise): TPromise { + return new TPromise((c, e) => promise.then(result => c(result), error => e(error)), () => this.logService.debug('Request Cancelled')); + } + private reportTelemetry(eventName: string, extensionData: any, duration: number, error?: Error): void { const errorcode = error ? error instanceof ExtensionManagementError ? error.code : ERROR_UNKNOWN : void 0; /* __GDPR__ From 8e03e031316991ebc1fa1be27ecee2d05cd1a805 Mon Sep 17 00:00:00 2001 From: zhuowei Date: Mon, 25 Jun 2018 12:30:59 -0400 Subject: [PATCH 034/228] Fix #35361: fix native tabs on macOS 10.13 (#52775) * Fix #35361: fix native tabs on macOS 10.13 macOS 10.13 mistakenly enables a compatibility option on VS Code and VS Code Insiders because their bundle IDs begin with "com.microsoft.". This breaks native tabs. Explicitly disable the compatibility patch using NSUserDefaults. Note that Code-OSS is not affected by the bug, since its bundle ID is "com.visualstudio.code.oss". To test this behaviour, change darwinBundleIdentifier in product.json. * set NSUseImprovedLayoutPass only when window.nativeTabs: true --- src/vs/code/electron-main/app.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index aebb464f8d2..0ef92976e16 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -5,7 +5,7 @@ 'use strict'; -import { app, ipcMain as ipc } from 'electron'; +import { app, ipcMain as ipc, systemPreferences } from 'electron'; import * as platform from 'vs/base/common/platform'; import { WindowsManager } from 'vs/code/electron-main/windows'; import { IWindowsService, OpenContext, ActiveWindowManager } from 'vs/platform/windows/common/windows'; @@ -85,7 +85,7 @@ export class CodeApplication { @ILogService private logService: ILogService, @IEnvironmentService private environmentService: IEnvironmentService, @ILifecycleService private lifecycleService: ILifecycleService, - @IConfigurationService configurationService: ConfigurationService, + @IConfigurationService private configurationService: ConfigurationService, @IStateService private stateService: IStateService, @IHistoryMainService private historyMainService: IHistoryMainService ) { @@ -274,6 +274,16 @@ export class CodeApplication { app.setAppUserModelId(product.win32AppUserModelId); } + // Fix native tabs on macOS 10.13 + // macOS enables a compatibility patch for any bundle ID beginning with + // "com.microsoft.", which breaks native tabs for VS Code when using this + // identifier (from the official build). + // Explicitly opt out of the patch here before creating any windows. + // See: https://github.com/Microsoft/vscode/issues/35361#issuecomment-399794085 + if (platform.isMacintosh && this.configurationService.getValue('window.nativeTabs') === true) { + systemPreferences.registerDefaults({ NSUseImprovedLayoutPass: true }); + } + // Create Electron IPC Server this.electronIpcServer = new ElectronIPCServer(); From 0a23bfb68f3ca1f02012fa0aa3e7b78de62987e9 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sun, 24 Jun 2018 11:22:34 -0700 Subject: [PATCH 035/228] Fix #51597 - move searching with absolute path queries out of search service and into openFileHandler --- .../parts/search/browser/openFileHandler.ts | 71 +++++----- .../services/search/node/fileSearch.ts | 129 +++++++----------- .../services/search/node/rawSearchService.ts | 19 +-- 3 files changed, 93 insertions(+), 126 deletions(-) diff --git a/src/vs/workbench/parts/search/browser/openFileHandler.ts b/src/vs/workbench/parts/search/browser/openFileHandler.ts index de87f36047e..591c9761758 100644 --- a/src/vs/workbench/parts/search/browser/openFileHandler.ts +++ b/src/vs/workbench/parts/search/browser/openFileHandler.ts @@ -26,7 +26,7 @@ import { EditorInput, IWorkbenchEditorConfiguration } from 'vs/workbench/common/ import { IResourceInput } from 'vs/platform/editor/common/editor'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IQueryOptions, ISearchService, ISearchStats, ISearchQuery } from 'vs/platform/search/common/search'; +import { IQueryOptions, ISearchService, ISearchStats, ISearchQuery, ISearchComplete } from 'vs/platform/search/common/search'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IRange } from 'vs/editor/common/core/range'; @@ -152,29 +152,44 @@ export class OpenFileHandler extends QuickOpenHandler { } private doFindResults(query: IPreparedQuery, cacheKey?: string, maxSortedResults?: number): TPromise { - return this.doResolveQueryOptions(query, cacheKey, maxSortedResults).then(queryOptions => { - let iconClass: string; - if (this.options && this.options.forceUseIcons && !this.themeService.getFileIconTheme()) { - iconClass = 'file'; // only use a generic file icon if we are forced to use an icon and have no icon theme set otherwise + let iconClass: string; + const queryOptions = this.doResolveQueryOptions(query, cacheKey, maxSortedResults); + if (this.options && this.options.forceUseIcons && !this.themeService.getFileIconTheme()) { + iconClass = 'file'; // only use a generic file icon if we are forced to use an icon and have no icon theme set otherwise + } + + return this.getAbsolutePathResult(query).then(result => { + // If the original search value is an existing file on disk, return it immediately and bypass the search service + if (result) { + return TPromise.wrap({ results: [{ resource: result }] }); + } else { + return this.searchService.search(this.queryBuilder.file(this.contextService.getWorkspace().folders.map(folder => folder.uri), queryOptions)); + } + }).then(complete => { + const results: QuickOpenEntry[] = []; + for (let i = 0; i < complete.results.length; i++) { + const fileMatch = complete.results[i]; + + const label = paths.basename(fileMatch.resource.fsPath); + const description = labels.getPathLabel(resources.dirname(fileMatch.resource), this.environmentService, this.contextService); + + results.push(this.instantiationService.createInstance(FileEntry, fileMatch.resource, label, description, iconClass)); } - return this.searchService.search(this.queryBuilder.file(this.contextService.getWorkspace().folders.map(folder => folder.uri), queryOptions)).then(complete => { - const results: QuickOpenEntry[] = []; - for (let i = 0; i < complete.results.length; i++) { - const fileMatch = complete.results[i]; - - const label = paths.basename(fileMatch.resource.fsPath); - const description = labels.getPathLabel(resources.dirname(fileMatch.resource), this.environmentService, this.contextService); - - results.push(this.instantiationService.createInstance(FileEntry, fileMatch.resource, label, description, iconClass)); - } - - return new FileQuickOpenModel(results, complete.stats); - }); + return new FileQuickOpenModel(results, complete.stats); }); } - private doResolveQueryOptions(query: IPreparedQuery, cacheKey?: string, maxSortedResults?: number): TPromise { + private getAbsolutePathResult(query: IPreparedQuery): TPromise { + if (paths.isAbsolute(query.original)) { + const resource = URI.file(query.original); + return this.fileService.resolveFile(resource).then(stat => stat.isDirectory ? void 0 : resource, error => void 0); + } else { + return TPromise.as(null); + } + } + + private doResolveQueryOptions(query: IPreparedQuery, cacheKey?: string, maxSortedResults?: number): IQueryOptions { const queryOptions: IQueryOptions = { extraFileResources: getOutOfWorkspaceEditorResources(this.editorService, this.contextService), filePattern: query.value, @@ -186,23 +201,7 @@ export class OpenFileHandler extends QuickOpenHandler { queryOptions.sortByScore = true; } - let queryIsAbsoluteFilePromise: TPromise; - if (paths.isAbsolute(query.original)) { - const resource = URI.file(query.original); - queryIsAbsoluteFilePromise = this.fileService.resolveFile(resource).then(stat => stat.isDirectory ? void 0 : resource, error => void 0); - } else { - queryIsAbsoluteFilePromise = TPromise.as(null); - } - - return queryIsAbsoluteFilePromise.then(resource => { - if (resource) { - // if the original search value is an existing file on disk, add it to the - // extra file resources to consider (fixes https://github.com/Microsoft/vscode/issues/42726) - queryOptions.extraFileResources.push(resource); - } - - return queryOptions; - }); + return queryOptions; } public hasShortResponseTime(): boolean { diff --git a/src/vs/workbench/services/search/node/fileSearch.ts b/src/vs/workbench/services/search/node/fileSearch.ts index 0652e2239be..1182c79e506 100644 --- a/src/vs/workbench/services/search/node/fileSearch.ts +++ b/src/vs/workbench/services/search/node/fileSearch.ts @@ -123,78 +123,61 @@ export class FileWalker { this.fileWalkStartTime = Date.now(); // Support that the file pattern is a full path to a file that exists - this.checkFilePatternAbsoluteMatch((exists, size) => { - if (this.isCanceled) { - return done(null, this.isLimitHit); - } + if (this.isCanceled) { + return done(null, this.isLimitHit); + } - // Report result from file pattern if matching - if (exists) { - this.resultCount++; - onResult({ - relativePath: this.filePattern, - basename: path.basename(this.filePattern), - size - }); - - // Optimization: a match on an absolute path is a good result and we do not - // continue walking the entire root paths array for other matches because - // it is very unlikely that another file would match on the full absolute path - return done(null, this.isLimitHit); - } - - // For each extra file - if (extraFiles) { - extraFiles.forEach(extraFilePath => { - const basename = path.basename(extraFilePath); - if (this.globalExcludePattern && this.globalExcludePattern(extraFilePath, basename)) { - return; // excluded - } - - // File: Check for match on file pattern and include pattern - this.matchFile(onResult, { relativePath: extraFilePath /* no workspace relative path */, basename }); - }); - } - - let traverse = this.nodeJSTraversal; - if (!this.maxFilesize) { - if (this.useRipgrep) { - this.traversal = Traversal.Ripgrep; - traverse = this.cmdTraversal; - } else if (platform.isMacintosh) { - this.traversal = Traversal.MacFind; - traverse = this.cmdTraversal; - // Disable 'dir' for now (#11181, #11179, #11183, #11182). - } /* else if (platform.isWindows) { - this.traversal = Traversal.WindowsDir; - traverse = this.windowsDirTraversal; - } */ else if (platform.isLinux) { - this.traversal = Traversal.LinuxFind; - traverse = this.cmdTraversal; + // For each extra file + if (extraFiles) { + extraFiles.forEach(extraFilePath => { + const basename = path.basename(extraFilePath); + if (this.globalExcludePattern && this.globalExcludePattern(extraFilePath, basename)) { + return; // excluded } - } - const isNodeTraversal = traverse === this.nodeJSTraversal; - if (!isNodeTraversal) { - this.cmdForkStartTime = Date.now(); - } - - // For each root folder - flow.parallel(folderQueries, (folderQuery: IFolderSearch, rootFolderDone: (err: Error, result: void) => void) => { - this.call(traverse, this, folderQuery, onResult, onMessage, (err?: Error) => { - if (err) { - const errorMessage = toErrorMessage(err); - console.error(errorMessage); - this.errors.push(errorMessage); - rootFolderDone(err, undefined); - } else { - rootFolderDone(undefined, undefined); - } - }); - }, (errors, result) => { - const err = errors ? errors.filter(e => !!e)[0] : null; - done(err, this.isLimitHit); + // File: Check for match on file pattern and include pattern + this.matchFile(onResult, { relativePath: extraFilePath /* no workspace relative path */, basename }); }); + } + + let traverse = this.nodeJSTraversal; + if (!this.maxFilesize) { + if (this.useRipgrep) { + this.traversal = Traversal.Ripgrep; + traverse = this.cmdTraversal; + } else if (platform.isMacintosh) { + this.traversal = Traversal.MacFind; + traverse = this.cmdTraversal; + // Disable 'dir' for now (#11181, #11179, #11183, #11182). + } /* else if (platform.isWindows) { + this.traversal = Traversal.WindowsDir; + traverse = this.windowsDirTraversal; + } */ else if (platform.isLinux) { + this.traversal = Traversal.LinuxFind; + traverse = this.cmdTraversal; + } + } + + const isNodeTraversal = traverse === this.nodeJSTraversal; + if (!isNodeTraversal) { + this.cmdForkStartTime = Date.now(); + } + + // For each root folder + flow.parallel(folderQueries, (folderQuery: IFolderSearch, rootFolderDone: (err: Error, result: void) => void) => { + this.call(traverse, this, folderQuery, onResult, onMessage, (err?: Error) => { + if (err) { + const errorMessage = toErrorMessage(err); + console.error(errorMessage); + this.errors.push(errorMessage); + rootFolderDone(err, undefined); + } else { + rootFolderDone(undefined, undefined); + } + }); + }, (errors, result) => { + const err = errors ? errors.filter(e => !!e)[0] : null; + done(err, this.isLimitHit); }); } @@ -568,16 +551,6 @@ export class FileWalker { }; } - private checkFilePatternAbsoluteMatch(clb: (exists: boolean, size?: number) => void): void { - if (!this.filePattern || !path.isAbsolute(this.filePattern)) { - return clb(false); - } - - return fs.stat(this.filePattern, (error, stat) => { - return clb(!error && !stat.isDirectory(), stat && stat.size); // only existing files - }); - } - private checkFilePatternRelativeMatch(basePath: string, clb: (matchPath: string, size?: number) => void): void { if (!this.filePattern || path.isAbsolute(this.filePattern)) { return clb(null); diff --git a/src/vs/workbench/services/search/node/rawSearchService.ts b/src/vs/workbench/services/search/node/rawSearchService.ts index 7f8387e9d29..fd40541b99c 100644 --- a/src/vs/workbench/services/search/node/rawSearchService.ts +++ b/src/vs/workbench/services/search/node/rawSearchService.ts @@ -6,23 +6,22 @@ 'use strict'; import * as fs from 'fs'; -import { isAbsolute, sep, join } from 'path'; - import * as gracefulFs from 'graceful-fs'; -gracefulFs.gracefulify(fs); - +import { join, sep } from 'path'; import * as arrays from 'vs/base/common/arrays'; import * as objects from 'vs/base/common/objects'; import * as strings from 'vs/base/common/strings'; import { PPromise, TPromise } from 'vs/base/common/winjs.base'; -import { FileWalker, Engine as FileSearchEngine } from 'vs/workbench/services/search/node/fileSearch'; +import { compareItemsByScore, IItemAccessor, prepareQuery, ScorerCache } from 'vs/base/parts/quickopen/common/quickOpenScorer'; import { MAX_FILE_SIZE } from 'vs/platform/files/node/files'; +import { ICachedSearchStats, IProgress } from 'vs/platform/search/common/search'; +import { Engine as FileSearchEngine, FileWalker } from 'vs/workbench/services/search/node/fileSearch'; import { RipgrepEngine } from 'vs/workbench/services/search/node/ripgrepTextSearch'; import { Engine as TextSearchEngine } from 'vs/workbench/services/search/node/textSearch'; import { TextSearchWorkerProvider } from 'vs/workbench/services/search/node/textSearchWorkerProvider'; -import { IRawSearchService, IRawSearch, IRawFileMatch, ISerializedFileMatch, ISerializedSearchProgressItem, ISerializedSearchComplete, ISearchEngine, IFileSearchProgressItem, ITelemetryEvent } from './search'; -import { ICachedSearchStats, IProgress } from 'vs/platform/search/common/search'; -import { compareItemsByScore, IItemAccessor, ScorerCache, prepareQuery } from 'vs/base/parts/quickopen/common/quickOpenScorer'; +import { IFileSearchProgressItem, IRawFileMatch, IRawSearch, IRawSearchService, ISearchEngine, ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, ITelemetryEvent } from './search'; + +gracefulFs.gracefulify(fs); export class SearchService implements IRawSearchService { @@ -271,10 +270,6 @@ export class SearchService implements IRawSearchService { } private getResultsFromCache(cache: Cache, searchValue: string): PPromise<[ISerializedSearchComplete, IRawFileMatch[], CacheStats], IProgress> { - if (isAbsolute(searchValue)) { - return null; // bypass cache if user looks up an absolute path where matching goes directly on disk - } - // Find cache entries by prefix of search value const hasPathSep = searchValue.indexOf(sep) >= 0; let cached: PPromise<[ISerializedSearchComplete, IRawFileMatch[]], IFileSearchProgressItem>; From 1df0be9fc896f81945d672bcf3f745742f520f73 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 25 Jun 2018 09:34:56 -0700 Subject: [PATCH 036/228] #51597 - PR feedback --- src/vs/workbench/parts/search/browser/openFileHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/search/browser/openFileHandler.ts b/src/vs/workbench/parts/search/browser/openFileHandler.ts index 591c9761758..44473ebeb4e 100644 --- a/src/vs/workbench/parts/search/browser/openFileHandler.ts +++ b/src/vs/workbench/parts/search/browser/openFileHandler.ts @@ -152,8 +152,8 @@ export class OpenFileHandler extends QuickOpenHandler { } private doFindResults(query: IPreparedQuery, cacheKey?: string, maxSortedResults?: number): TPromise { - let iconClass: string; const queryOptions = this.doResolveQueryOptions(query, cacheKey, maxSortedResults); + let iconClass: string; if (this.options && this.options.forceUseIcons && !this.themeService.getFileIconTheme()) { iconClass = 'file'; // only use a generic file icon if we are forced to use an icon and have no icon theme set otherwise } From 9719d316ca50a4b613fc03403cae0adf6886f757 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 25 Jun 2018 19:03:21 +0200 Subject: [PATCH 037/228] Revert "Fix #35361: fix native tabs on macOS 10.13 (#52775)" This reverts commit 8e03e031316991ebc1fa1be27ecee2d05cd1a805. --- src/vs/code/electron-main/app.ts | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 0ef92976e16..aebb464f8d2 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -5,7 +5,7 @@ 'use strict'; -import { app, ipcMain as ipc, systemPreferences } from 'electron'; +import { app, ipcMain as ipc } from 'electron'; import * as platform from 'vs/base/common/platform'; import { WindowsManager } from 'vs/code/electron-main/windows'; import { IWindowsService, OpenContext, ActiveWindowManager } from 'vs/platform/windows/common/windows'; @@ -85,7 +85,7 @@ export class CodeApplication { @ILogService private logService: ILogService, @IEnvironmentService private environmentService: IEnvironmentService, @ILifecycleService private lifecycleService: ILifecycleService, - @IConfigurationService private configurationService: ConfigurationService, + @IConfigurationService configurationService: ConfigurationService, @IStateService private stateService: IStateService, @IHistoryMainService private historyMainService: IHistoryMainService ) { @@ -274,16 +274,6 @@ export class CodeApplication { app.setAppUserModelId(product.win32AppUserModelId); } - // Fix native tabs on macOS 10.13 - // macOS enables a compatibility patch for any bundle ID beginning with - // "com.microsoft.", which breaks native tabs for VS Code when using this - // identifier (from the official build). - // Explicitly opt out of the patch here before creating any windows. - // See: https://github.com/Microsoft/vscode/issues/35361#issuecomment-399794085 - if (platform.isMacintosh && this.configurationService.getValue('window.nativeTabs') === true) { - systemPreferences.registerDefaults({ NSUseImprovedLayoutPass: true }); - } - // Create Electron IPC Server this.electronIpcServer = new ElectronIPCServer(); From b0fb72f7663818a63840d96fb60b768b5ad9b858 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 25 Jun 2018 10:28:19 -0700 Subject: [PATCH 038/228] Remove 'absolute path' search service test which is now obsolete --- .../services/search/test/node/search.test.ts | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/src/vs/workbench/services/search/test/node/search.test.ts b/src/vs/workbench/services/search/test/node/search.test.ts index c4a69c77005..1d19c3d0c76 100644 --- a/src/vs/workbench/services/search/test/node/search.test.ts +++ b/src/vs/workbench/services/search/test/node/search.test.ts @@ -578,29 +578,6 @@ suite('FileSearchEngine', () => { }); }); - test('Files: absolute path to file ignores excludes', function (done: () => void) { - this.timeout(testTimeout); - let engine = new FileSearchEngine({ - folderQueries: ROOT_FOLDER_QUERY, - filePattern: path.normalize(path.join(require.toUrl('./fixtures'), 'site.css')), - excludePattern: { '**/*.css': true } - }); - - let count = 0; - let res: IRawFileMatch; - engine.search((result) => { - if (result) { - count++; - } - res = result; - }, () => { }, (error) => { - assert.ok(!error); - assert.equal(count, 1); - assert.equal(path.basename(res.relativePath), 'site.css'); - done(); - }); - }); - test('Files: relative path matched once', function (done: () => void) { this.timeout(testTimeout); let engine = new FileSearchEngine({ From 6ae1cc772012debfbcf4c921b9278b63fd19bba6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Tar?= Date: Mon, 25 Jun 2018 19:54:49 +0200 Subject: [PATCH 039/228] Fix a replace gone wrong in typescript-language-features/package.nls (#52704) --- extensions/typescript-language-features/package.nls.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/typescript-language-features/package.nls.json b/extensions/typescript-language-features/package.nls.json index f0053efc497..b221d624d26 100644 --- a/extensions/typescript-language-features/package.nls.json +++ b/extensions/typescript-language-features/package.nls.json @@ -46,7 +46,7 @@ "typescript.problemMatchers.tsc.label": "TypeScript problems", "typescript.problemMatchers.tscWatch.label": "TypeScript problems (watch mode)", "typescript.quickSuggestionsForPaths": "Enable/disable quick suggestions when typing out an import path.", - "typescript.locale": "Sets the locale used to report JavaScript and TypeScript errors. Requires using TypeScript 2.6.0. Default of 'null' uses VS Code's locale. or newer in the workspace.", + "typescript.locale": "Sets the locale used to report JavaScript and TypeScript errors. Requires using TypeScript 2.6.0 or newer in the workspace. Default of 'null' uses VS Code's locale.", "javascript.implicitProjectConfig.experimentalDecorators": "Enable/disable 'experimentalDecorators' for JavaScript files that are not part of a project. Existing jsconfig.json or tsconfig.json files override this setting. Requires using TypeScript 2.3.1 or newer in the workspace.", "typescript.autoImportSuggestions.enabled": "Enable/disable auto import suggestions. Requires using TypeScript 2.6.1 or newer in the workspace.", "taskDefinition.tsconfig.description": "The tsconfig file that defines the TS build.", @@ -56,4 +56,4 @@ "typescript.preferences.importModuleSpecifier": "Preferred path style for auto imports:\n- \"relative\" to the file location.\n- \"non-relative\" based on the 'baseUrl' configured in your 'jsconfig.json' / 'tsconfig.json'.\n- \"auto\" infer the shortest path type.\nRequires using TypeScript 2.9 or newer in the workspace.", "typescript.showUnused": "Enable/disable highlighting of unused variables in code. Requires using TypeScript 2.9 or newer in the workspace.", "typescript.updateImportsOnFileMove.enabled": "Enable/disable automatic updating of import paths when you rename or move a file in VS Code. Possible values are: 'prompt' on each rename, 'always' update paths automatically, and 'never' rename paths and don't prompt me. Requires using TypeScript 2.9 or newer in the workspace." -} \ No newline at end of file +} From fdb62c52ff2e4ae594009353b57c4261b7df9679 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 25 Jun 2018 11:06:32 -0700 Subject: [PATCH 040/228] Ensure terminal scrollbar is visible when find widget is focused Fixes #29670 --- src/vs/editor/contrib/find/simpleFindWidget.ts | 4 ++++ .../parts/terminal/electron-browser/media/scrollbar.css | 1 + .../parts/terminal/electron-browser/terminalInstance.ts | 1 + .../parts/terminal/electron-browser/terminalPanel.ts | 4 ++++ 4 files changed, 10 insertions(+) diff --git a/src/vs/editor/contrib/find/simpleFindWidget.ts b/src/vs/editor/contrib/find/simpleFindWidget.ts index 4df4247d74c..fdb0b83104c 100644 --- a/src/vs/editor/contrib/find/simpleFindWidget.ts +++ b/src/vs/editor/contrib/find/simpleFindWidget.ts @@ -133,6 +133,10 @@ export abstract class SimpleFindWidget extends Widget { return this._findInput.getValue(); } + public get focusTracker(): dom.IFocusTracker { + return this._findInputFocusTracker; + } + public updateTheme(theme: ITheme): void { const inputStyles = { inputActiveOptionBorder: theme.getColor(inputActiveOptionBorder), diff --git a/src/vs/workbench/parts/terminal/electron-browser/media/scrollbar.css b/src/vs/workbench/parts/terminal/electron-browser/media/scrollbar.css index 55ccd4c3e6c..0d28e3564d1 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/media/scrollbar.css +++ b/src/vs/workbench/parts/terminal/electron-browser/media/scrollbar.css @@ -24,6 +24,7 @@ background-color: inherit; } +.monaco-workbench .panel.integrated-terminal .find-focused .xterm .xterm-viewport, .monaco-workbench .panel.integrated-terminal .xterm.focus .xterm-viewport, .monaco-workbench .panel.integrated-terminal .xterm:focus .xterm-viewport, .monaco-workbench .panel.integrated-terminal .xterm:hover .xterm-viewport { diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts index 6203b5c5c97..6d9978c6211 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts @@ -1069,6 +1069,7 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const scrollbarSliderBackgroundColor = theme.getColor(scrollbarSliderBackground); if (scrollbarSliderBackgroundColor) { collector.addRule(` + .monaco-workbench .panel.integrated-terminal .find-focused .xterm .xterm-viewport, .monaco-workbench .panel.integrated-terminal .xterm.focus .xterm-viewport, .monaco-workbench .panel.integrated-terminal .xterm:focus .xterm-viewport, .monaco-workbench .panel.integrated-terminal .xterm:hover .xterm-viewport { background-color: ${scrollbarSliderBackgroundColor} !important; }` diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts index 671a3342519..76750995eac 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts @@ -28,6 +28,8 @@ import { DataTransfers } from 'vs/base/browser/dnd'; import { INotificationService, IPromptChoice, Severity } from 'vs/platform/notification/common/notification'; import { TerminalConfigHelper } from 'vs/workbench/parts/terminal/electron-browser/terminalConfigHelper'; +const FIND_FOCUS_CLASS = 'find-focused'; + export class TerminalPanel extends Panel { private _actions: IAction[]; @@ -61,6 +63,8 @@ export class TerminalPanel extends Panel { dom.addClass(this._terminalContainer, 'terminal-outer-container'); this._findWidget = this._instantiationService.createInstance(TerminalFindWidget); + this._findWidget.focusTracker.onDidFocus(() => this._terminalContainer.classList.add(FIND_FOCUS_CLASS)); + this._findWidget.focusTracker.onDidBlur(() => this._terminalContainer.classList.remove(FIND_FOCUS_CLASS)); this._parentDomElement.appendChild(this._fontStyleElement); this._parentDomElement.appendChild(this._terminalContainer); From 946ff76af650fcd1c7a0d34afafb8f5058613bb8 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 21 Jun 2018 14:05:43 -0700 Subject: [PATCH 041/228] Fix jsdoc comment being on wrong type --- src/vs/base/common/objects.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/base/common/objects.ts b/src/vs/base/common/objects.ts index 31769c963f1..513ac6c9240 100644 --- a/src/vs/base/common/objects.ts +++ b/src/vs/base/common/objects.ts @@ -225,6 +225,7 @@ export function getOrDefault(obj: T, fn: (obj: T) => R, defaultValue: R = return typeof result === 'undefined' ? defaultValue : result; } +type obj = { [key: string]: any; }; /** * Returns an object that has keys for each value that is different in the base object. Keys * that do not exist in the target but in the base object are not considered. @@ -235,7 +236,6 @@ export function getOrDefault(obj: T, fn: (obj: T) => R, defaultValue: R = * @param base the object to diff against * @param obj the object to use for diffing */ -export type obj = { [key: string]: any; }; export function distinct(base: obj, target: obj): obj { const result = Object.create(null); From 48c54a009f8c3bc20a574c1d0b2a287813cd657b Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 25 Jun 2018 11:08:51 -0700 Subject: [PATCH 042/228] Override light color for function and params in md code preview Fixes #52710 --- extensions/markdown-language-features/media/highlight.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/extensions/markdown-language-features/media/highlight.css b/extensions/markdown-language-features/media/highlight.css index fd39af662c0..3b748b1c4f9 100644 --- a/extensions/markdown-language-features/media/highlight.css +++ b/extensions/markdown-language-features/media/highlight.css @@ -120,6 +120,11 @@ Visual Studio-like style based on original C# coloring by Jason Diamond Date: Mon, 25 Jun 2018 20:15:35 +0200 Subject: [PATCH 043/228] fix #52807 --- .../services/editor/browser/editorService.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index 4feecb72897..eed776b4b8b 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -25,7 +25,7 @@ import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { localize } from 'vs/nls'; import { TPromise } from 'vs/base/common/winjs.base'; import { IEditorGroupsService, IEditorGroup, GroupsOrder, IEditorReplacement, GroupChangeKind, preferredSideBySideGroupDirection } from 'vs/workbench/services/group/common/editorGroupsService'; -import { IResourceEditor, ACTIVE_GROUP_TYPE, SIDE_GROUP_TYPE, SIDE_GROUP, ACTIVE_GROUP, IResourceEditorReplacement, IOpenEditorOverrideHandler } from 'vs/workbench/services/editor/common/editorService'; +import { IResourceEditor, ACTIVE_GROUP_TYPE, SIDE_GROUP_TYPE, SIDE_GROUP, IResourceEditorReplacement, IOpenEditorOverrideHandler } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Disposable, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { coalesce } from 'vs/base/common/arrays'; @@ -260,18 +260,13 @@ export class EditorService extends Disposable implements EditorServiceImpl { return group; } - // Group: Active Group - if (group === ACTIVE_GROUP) { - targetGroup = this.editorGroupService.activeGroup; - } - // Group: Side by Side - else if (group === SIDE_GROUP) { + if (group === SIDE_GROUP) { targetGroup = this.findSideBySideGroup(); } // Group: Specific Group - else if (typeof group === 'number') { + else if (typeof group === 'number' && group >= 0) { targetGroup = this.editorGroupService.getGroup(group); } From b577ccebe7bc93ab468ebe3329424d7872221549 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 25 Jun 2018 11:11:01 -0700 Subject: [PATCH 044/228] Fix #52817 - also consider settings tree selection when syncing with the TOC --- .../preferences/browser/settingsEditor2.ts | 64 ++++++++++++------- 1 file changed, 40 insertions(+), 24 deletions(-) diff --git a/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts b/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts index d898824c849..77dbe5331ab 100644 --- a/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts @@ -328,31 +328,12 @@ export class SettingsEditor2 extends BaseEditor { this.selectedElement = e.focus; })); + this._register(this.settingsTree.onDidChangeSelection(() => { + this.updateTreeScrollSync(); + })); + this._register(this.settingsTree.onDidScroll(() => { - if (this.searchResultModel) { - return; - } - - if (!this.tocTree.getInput()) { - return; - } - - const topElement = this.settingsTree.getFirstVisibleElement(); - const element = topElement instanceof SettingsTreeSettingElement ? topElement.parent : - topElement instanceof SettingsTreeGroupElement ? topElement : - null; - - if (element && this.tocTree.getSelection()[0] !== element) { - const elementTop = this.tocTree.getRelativeTop(element); - if (elementTop < 0) { - this.tocTree.reveal(element, 0); - } else if (elementTop > 1) { - this.tocTree.reveal(element, 1); - } - - this.tocTree.setSelection([element], { fromScroll: true }); - this.tocTree.setFocus(element); - } + this.updateTreeScrollSync(); })); } @@ -385,6 +366,41 @@ export class SettingsEditor2 extends BaseEditor { this.settingUpdateDelayer.trigger(() => this.updateChangedSetting(key, value)); } + private updateTreeScrollSync(): void { + if (this.searchResultModel) { + return; + } + + if (!this.tocTree.getInput()) { + return; + } + + let elementToSync = this.settingsTree.getFirstVisibleElement(); + const selection = this.settingsTree.getSelection()[0]; + if (selection) { + const selectionPos = this.settingsTree.getRelativeTop(selection); + if (selectionPos >= 0 && selectionPos <= 1) { + elementToSync = selection; + } + } + + const element = elementToSync instanceof SettingsTreeSettingElement ? elementToSync.parent : + elementToSync instanceof SettingsTreeGroupElement ? elementToSync : + null; + + if (element && this.tocTree.getSelection()[0] !== element) { + const elementTop = this.tocTree.getRelativeTop(element); + if (elementTop < 0) { + this.tocTree.reveal(element, 0); + } else if (elementTop > 1) { + this.tocTree.reveal(element, 1); + } + + this.tocTree.setSelection([element], { fromScroll: true }); + this.tocTree.setFocus(element); + } + } + private updateChangedSetting(key: string, value: any): TPromise { // ConfigurationService displays the error if this fails. // Force a render afterwards because onDidConfigurationUpdate doesn't fire if the update doesn't result in an effective setting value change From d66d3dd7a0893bf17aa64f2ca8b9b28949e3f702 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 25 Jun 2018 11:29:16 -0700 Subject: [PATCH 045/228] Fix #52809 - add setting to hide TOC entirely --- .../electron-browser/main.contribution.ts | 7 ++++++- .../preferences/browser/media/settingsEditor2.css | 5 +++-- .../parts/preferences/browser/settingsEditor2.ts | 15 ++++++++++++++- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/electron-browser/main.contribution.ts b/src/vs/workbench/electron-browser/main.contribution.ts index b5b9b6e9ba1..12a7cafd10f 100644 --- a/src/vs/workbench/electron-browser/main.contribution.ts +++ b/src/vs/workbench/electron-browser/main.contribution.ts @@ -310,8 +310,13 @@ configurationRegistry.registerConfiguration({ 'workbench.settings.settingsSearchTocBehavior': { 'type': 'string', 'enum': ['hide', 'filter', 'show'], - 'description': nls.localize('settingsSearchTocBehavior', "Controls the behavior of the settings editor TOC while searching."), + 'description': nls.localize('settingsSearchTocBehavior', "Controls the behavior of the settings editor Table of Contents while searching."), 'default': 'hide' + }, + 'workbench.settings.tocVisible': { + 'type': 'boolean', + 'description': nls.localize('settingsTocVisible', "Controls whether the settings editor Table of Contents is visible."), + 'default': true } } }); diff --git a/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css index 9b08dd0e458..ac698aadab9 100644 --- a/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css +++ b/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css @@ -111,10 +111,11 @@ .settings-editor > .settings-body .settings-toc-container { width: 175px; margin-right: 5px; + padding-top: 5px; } -.settings-editor > .settings-body .settings-toc-container { - padding-top: 5px; +.settings-editor > .settings-body .settings-toc-container.hidden { + display: none; } .search-mode .settings-toc-container { diff --git a/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts b/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts index 77dbe5331ab..a850561da36 100644 --- a/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts @@ -99,7 +99,13 @@ export class SettingsEditor2 extends BaseEditor { this.inSettingsEditorContextKey = CONTEXT_SETTINGS_EDITOR.bindTo(contextKeyService); this.searchFocusContextKey = CONTEXT_SETTINGS_SEARCH_FOCUS.bindTo(contextKeyService); - this._register(configurationService.onDidChangeConfiguration(() => this.onConfigUpdate())); + this._register(configurationService.onDidChangeConfiguration(e => { + this.onConfigUpdate(); + + if (e.affectsConfiguration('workbench.settings.tocVisible')) { + this.updateTOCVisible(); + } + })); } createEditor(parent: HTMLElement): void { @@ -259,6 +265,13 @@ export class SettingsEditor2 extends BaseEditor { } } })); + + this.updateTOCVisible(); + } + + private updateTOCVisible(): void { + const visible = !!this.configurationService.getValue('workbench.settings.tocVisible'); + DOM.toggleClass(this.tocTreeContainer, 'hidden', !visible); } private createSettingsTree(parent: HTMLElement): void { From 4e4e6f9b44dd9e6a53d1f08565c52b8ea9f61a5e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 25 Jun 2018 11:51:45 -0700 Subject: [PATCH 046/228] node-pty@0.7.6 --- package.json | 4 ++-- yarn.lock | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 9a84ea770b7..4eeb6524732 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "native-is-elevated": "^0.2.1", "native-keymap": "1.2.5", "native-watchdog": "0.3.0", - "node-pty": "0.7.5", + "node-pty": "0.7.6", "semver": "^5.5.0", "spdlog": "0.6.0", "sudo-prompt": "8.2.0", @@ -137,4 +137,4 @@ "windows-mutex": "^0.2.0", "windows-process-tree": "0.2.2" } -} \ No newline at end of file +} diff --git a/yarn.lock b/yarn.lock index 3c5efb1299a..5063f04ed15 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3974,9 +3974,9 @@ node-abi@^2.2.0: dependencies: semver "^5.4.1" -node-pty@0.7.5: - version "0.7.5" - resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.7.5.tgz#dd7fa8df90a5bf7c5f5273ec5aeecabcbfbc921f" +node-pty@0.7.6: + version "0.7.6" + resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.7.6.tgz#bff6148c9c5836ca7e73c7aaaec067dcbdac2f7b" dependencies: nan "2.10.0" From b3e7079ddade55de8d88b6704cc515fdbec9b968 Mon Sep 17 00:00:00 2001 From: Pine Wu Date: Mon, 25 Jun 2018 11:52:09 -0700 Subject: [PATCH 047/228] [css] Update css-language-service for css/html --- extensions/css-language-features/server/package.json | 2 +- extensions/css-language-features/server/yarn.lock | 6 +++--- extensions/html-language-features/server/package.json | 2 +- extensions/html-language-features/server/yarn.lock | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/extensions/css-language-features/server/package.json b/extensions/css-language-features/server/package.json index 661b18831b2..ed0be7c6d2a 100644 --- a/extensions/css-language-features/server/package.json +++ b/extensions/css-language-features/server/package.json @@ -8,7 +8,7 @@ "node": "*" }, "dependencies": { - "vscode-css-languageservice": "^3.0.9-next.18", + "vscode-css-languageservice": "^3.0.9-next.19", "vscode-languageserver": "^4.1.3", "vscode-languageserver-protocol-foldingprovider": "^2.0.1" }, diff --git a/extensions/css-language-features/server/yarn.lock b/extensions/css-language-features/server/yarn.lock index 7003f400a78..13f96b274f8 100644 --- a/extensions/css-language-features/server/yarn.lock +++ b/extensions/css-language-features/server/yarn.lock @@ -194,9 +194,9 @@ supports-color@5.4.0: dependencies: has-flag "^3.0.0" -vscode-css-languageservice@^3.0.9-next.18: - version "3.0.9-next.18" - resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-3.0.9-next.18.tgz#f8f25123b5a8cdc9f72fafcd2c0088f726322437" +vscode-css-languageservice@^3.0.9-next.19: + version "3.0.9-next.19" + resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-3.0.9-next.19.tgz#193b1e0aa87c28d2b0dbe6ddd655bfe10501ae68" dependencies: vscode-languageserver-types "^3.7.2" vscode-nls "^3.2.2" diff --git a/extensions/html-language-features/server/package.json b/extensions/html-language-features/server/package.json index 8dc64310848..17f8da1090d 100644 --- a/extensions/html-language-features/server/package.json +++ b/extensions/html-language-features/server/package.json @@ -8,7 +8,7 @@ "node": "*" }, "dependencies": { - "vscode-css-languageservice": "^3.0.9-next.18", + "vscode-css-languageservice": "^3.0.9-next.19", "vscode-html-languageservice": "^2.1.3-next.5", "vscode-languageserver": "^4.1.3", "vscode-languageserver-protocol-foldingprovider": "^2.0.1", diff --git a/extensions/html-language-features/server/yarn.lock b/extensions/html-language-features/server/yarn.lock index a239f421cdc..a0d7b2e6efe 100644 --- a/extensions/html-language-features/server/yarn.lock +++ b/extensions/html-language-features/server/yarn.lock @@ -194,9 +194,9 @@ supports-color@5.4.0: dependencies: has-flag "^3.0.0" -vscode-css-languageservice@^3.0.9-next.18: - version "3.0.9-next.18" - resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-3.0.9-next.18.tgz#f8f25123b5a8cdc9f72fafcd2c0088f726322437" +vscode-css-languageservice@^3.0.9-next.19: + version "3.0.9-next.19" + resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-3.0.9-next.19.tgz#193b1e0aa87c28d2b0dbe6ddd655bfe10501ae68" dependencies: vscode-languageserver-types "^3.7.2" vscode-nls "^3.2.2" From b3e816ed619e577310f7ee320a789464f8b6445e Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 25 Jun 2018 11:34:41 -0700 Subject: [PATCH 048/228] Settings editor settings should be 'window' scope --- src/vs/workbench/electron-browser/main.contribution.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/electron-browser/main.contribution.ts b/src/vs/workbench/electron-browser/main.contribution.ts index 12a7cafd10f..9e0b17e9720 100644 --- a/src/vs/workbench/electron-browser/main.contribution.ts +++ b/src/vs/workbench/electron-browser/main.contribution.ts @@ -305,18 +305,21 @@ configurationRegistry.registerConfiguration({ 'workbench.settings.enableNaturalLanguageSearch': { 'type': 'boolean', 'description': nls.localize('enableNaturalLanguageSettingsSearch', "Controls whether to enable the natural language search mode for settings."), - 'default': true + 'default': true, + 'scope': ConfigurationScope.WINDOW }, 'workbench.settings.settingsSearchTocBehavior': { 'type': 'string', 'enum': ['hide', 'filter', 'show'], 'description': nls.localize('settingsSearchTocBehavior', "Controls the behavior of the settings editor Table of Contents while searching."), - 'default': 'hide' + 'default': 'hide', + 'scope': ConfigurationScope.WINDOW }, 'workbench.settings.tocVisible': { 'type': 'boolean', 'description': nls.localize('settingsTocVisible', "Controls whether the settings editor Table of Contents is visible."), - 'default': true + 'default': true, + 'scope': ConfigurationScope.WINDOW } } }); From f86e10a45fc35fc6081da10bbae86758ddca860a Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 25 Jun 2018 11:59:47 -0700 Subject: [PATCH 049/228] Fix #52808 - remove 'reset' button --- .../browser/media/settingsEditor2.css | 26 ------------------ .../preferences/browser/settingsEditor2.ts | 22 +++++++++++---- .../parts/preferences/browser/settingsTree.ts | 27 +------------------ 3 files changed, 18 insertions(+), 57 deletions(-) diff --git a/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css index ac698aadab9..031fe1744d5 100644 --- a/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css +++ b/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css @@ -202,15 +202,6 @@ display: flex; } -.settings-editor > .settings-body > .settings-tree-container .setting-item-bool > .reset-button-container { - margin-top: 5px; -} - -.settings-editor > .settings-body > .settings-tree-container .setting-item-bool > .reset-button-container > .setting-reset-button { - width: initial; - padding-left: 0px; -} - .settings-editor > .settings-body > .settings-tree-container .setting-item-bool .setting-value-checkbox { height: 16px; width: 16px; @@ -258,29 +249,12 @@ padding-left: 0px; } -.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-value > .setting-type-complex + .reset-button-container > .setting-reset-button.monaco-button { - display: none; -} - .settings-editor > .settings-body > .settings-tree-container .setting-item .monaco-select-box { width: initial; font: inherit; height: 26px; } -.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-reset-button.monaco-button { - text-align: left; - display: inline-block; - visibility: hidden; - width: initial; - - padding-top: 2px; -} - -.settings-editor > .settings-body > .settings-tree-container .setting-item.is-configured .setting-reset-button.monaco-button { - visibility: visible; -} - .settings-editor > .settings-body > .settings-tree-container .group-title, .settings-editor > .settings-body > .settings-tree-container .setting-item { padding-left: 10px; diff --git a/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts b/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts index a850561da36..7d26a796a44 100644 --- a/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts @@ -6,18 +6,19 @@ import * as DOM from 'vs/base/browser/dom'; import { Button } from 'vs/base/browser/ui/button/button'; import * as arrays from 'vs/base/common/arrays'; -import * as collections from 'vs/base/common/collections'; import { Delayer, ThrottledDelayer } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; +import * as collections from 'vs/base/common/collections'; import { Color } from 'vs/base/common/color'; import { getErrorMessage, isPromiseCanceledError } from 'vs/base/common/errors'; import { KeyCode } from 'vs/base/common/keyCodes'; +import URI from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import { ITree, ITreeConfiguration } from 'vs/base/parts/tree/browser/tree'; import { DefaultTreestyler, OpenMode } from 'vs/base/parts/tree/browser/treeDefaults'; import 'vs/css!./media/settingsEditor2'; import { localize } from 'vs/nls'; -import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationTarget, IConfigurationOverrides, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -30,8 +31,8 @@ import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { EditorOptions, IEditor } from 'vs/workbench/common/editor'; import { SearchWidget, SettingsTarget, SettingsTargetsWidget } from 'vs/workbench/parts/preferences/browser/preferencesWidgets'; -import { tocData, commonlyUsedData } from 'vs/workbench/parts/preferences/browser/settingsLayout'; -import { ISettingsEditorViewState, SearchResultIdx, SearchResultModel, SettingsAccessibilityProvider, SettingsDataSource, SettingsRenderer, SettingsTreeController, SettingsTreeElement, SettingsTreeFilter, SettingsTreeModel, SettingsTreeSettingElement, SettingsTreeGroupElement, resolveSettingsTree, NonExpandableTree, resolveExtensionsSettings } from 'vs/workbench/parts/preferences/browser/settingsTree'; +import { commonlyUsedData, tocData } from 'vs/workbench/parts/preferences/browser/settingsLayout'; +import { ISettingsEditorViewState, NonExpandableTree, resolveExtensionsSettings, resolveSettingsTree, SearchResultIdx, SearchResultModel, SettingsAccessibilityProvider, SettingsDataSource, SettingsRenderer, SettingsTreeController, SettingsTreeElement, SettingsTreeFilter, SettingsTreeGroupElement, SettingsTreeModel, SettingsTreeSettingElement } from 'vs/workbench/parts/preferences/browser/settingsTree'; import { TOCDataSource, TOCRenderer, TOCTreeModel } from 'vs/workbench/parts/preferences/browser/tocTree'; import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_SEARCH_FOCUS, IPreferencesSearchService, ISearchProvider } from 'vs/workbench/parts/preferences/common/preferences'; import { IPreferencesService, ISearchResult, ISettingsEditorModel } from 'vs/workbench/services/preferences/common/preferences'; @@ -417,7 +418,18 @@ export class SettingsEditor2 extends BaseEditor { private updateChangedSetting(key: string, value: any): TPromise { // ConfigurationService displays the error if this fails. // Force a render afterwards because onDidConfigurationUpdate doesn't fire if the update doesn't result in an effective setting value change - return this.configurationService.updateValue(key, value, this.settingsTargetsWidget.settingsTarget) + const settingsTarget = this.settingsTargetsWidget.settingsTarget; + const resource = URI.isUri(settingsTarget) ? settingsTarget : undefined; + const configurationTarget = (resource ? undefined : settingsTarget); + const overrides: IConfigurationOverrides = { resource }; + + // If the user is changing the value back to the default, do a 'reset' instead + const inspected = this.configurationService.inspect(key, overrides); + if (inspected.default === value) { + value = undefined; + } + + return this.configurationService.updateValue(key, value, overrides, configurationTarget) .then(() => this.refreshTreeAndMaintainFocus()) .then(() => { const reportModifiedProps = { diff --git a/src/vs/workbench/parts/preferences/browser/settingsTree.ts b/src/vs/workbench/parts/preferences/browser/settingsTree.ts index 9a770273f82..c2492110f96 100644 --- a/src/vs/workbench/parts/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/parts/preferences/browser/settingsTree.ts @@ -24,7 +24,7 @@ import { localize } from 'vs/nls'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { WorkbenchTree, WorkbenchTreeController } from 'vs/platform/list/browser/listService'; -import { editorActiveLinkForeground, registerColor, selectBackground, selectBorder } from 'vs/platform/theme/common/colorRegistry'; +import { registerColor, selectBackground, selectBorder } from 'vs/platform/theme/common/colorRegistry'; import { attachButtonStyler, attachInputBoxStyler, attachSelectBoxStyler } from 'vs/platform/theme/common/styler'; import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { SettingsTarget } from 'vs/workbench/parts/preferences/browser/preferencesWidgets'; @@ -405,7 +405,6 @@ interface ISettingItemTemplate extends IDisposableTemplate { labelElement: HTMLElement; descriptionElement: HTMLElement; controlElement: HTMLElement; - resetButtonElement: HTMLElement; isConfiguredElement: HTMLElement; otherOverridesElement: HTMLElement; } @@ -419,7 +418,6 @@ interface ISettingBoolItemTemplate extends IDisposableTemplate { labelElement: HTMLElement; descriptionElement: HTMLElement; checkbox: Checkbox; - resetButtonElement: HTMLElement; isConfiguredElement: HTMLElement; otherOverridesElement: HTMLElement; } @@ -556,7 +554,6 @@ export class SettingsRenderer implements IRenderer { labelElement, descriptionElement, controlElement, - resetButtonElement, isConfiguredElement, otherOverridesElement }; @@ -589,8 +586,6 @@ export class SettingsRenderer implements IRenderer { const controlElement = DOM.append(descriptionAndValueElement, $('.setting-item-bool-control')); const descriptionElement = DOM.append(descriptionAndValueElement, $('.setting-item-description')); - const resetButtonElement = DOM.append(container, $('.reset-button-container')); - const toDispose = []; const checkbox = new Checkbox({ actionClassName: 'setting-value-checkbox', isChecked: true, title: '', inputActiveOptionBorder: null }); controlElement.appendChild(checkbox.domNode); @@ -610,7 +605,6 @@ export class SettingsRenderer implements IRenderer { labelElement, checkbox, descriptionElement, - resetButtonElement, isConfiguredElement, otherOverridesElement }; @@ -673,25 +667,6 @@ export class SettingsRenderer implements IRenderer { this.renderValue(element, isSelected, template); - template.resetButtonElement.innerHTML = ''; - const resetButton = new Button(template.resetButtonElement); - const resetText = localize('resetButtonTitle', "reset"); - resetButton.label = resetText; - resetButton.element.title = resetText; - resetButton.element.classList.add('setting-reset-button'); - resetButton.element.tabIndex = isSelected ? 0 : -1; - - template.toDispose.push(attachButtonStyler(resetButton, this.themeService, { - buttonBackground: Color.transparent.toString(), - buttonHoverBackground: Color.transparent.toString(), - buttonForeground: editorActiveLinkForeground - })); - - template.toDispose.push(resetButton.onDidClick(e => { - this._onDidChangeSetting.fire({ key: element.setting.key, value: undefined }); - })); - template.toDispose.push(resetButton); - template.isConfiguredElement.textContent = element.isConfigured ? localize('configured', "Modified") : ''; if (element.overriddenScopeList.length) { From c9b7b4d9e0708bddf88bc67f283753f13aa0f837 Mon Sep 17 00:00:00 2001 From: Ramya Achutha Rao Date: Mon, 25 Jun 2018 12:22:55 -0700 Subject: [PATCH 050/228] Case insensitive check to avoid recommending multiple times Fixes #52753 --- .../extensions/electron-browser/extensionTipsService.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts index 6d9c8399e3d..20ed595d6b6 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts @@ -52,6 +52,9 @@ interface IDynamicWorkspaceRecommendations { } function caseInsensitiveGet(obj: { [key: string]: T }, key: string): T | undefined { + if (!obj) { + return undefined; + } for (const _key in obj) { if (obj.hasOwnProperty(_key) && _key.toLowerCase() === key.toLowerCase()) { return obj[_key]; @@ -515,7 +518,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe let { key: pattern, value: ids } = entry; if (match(pattern, uri.path)) { for (let id of ids) { - if (Object.keys(product.extensionImportantTips || []).map(x => x.toLowerCase()).indexOf(id.toLowerCase()) > -1) { + if (caseInsensitiveGet(product.extensionImportantTips, id)) { recommendationsToSuggest.push(id); } const filedBasedRecommendation = this._fileBasedRecommendations[id.toLowerCase()] || { recommendedTime: now, sources: [] }; @@ -543,7 +546,8 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe const server = this.extensionManagementServiceService.getExtensionManagementServer(model.uri); const importantTipsPromise = recommendationsToSuggest.length === 0 ? TPromise.as(null) : server.extensionManagementService.getInstalled(LocalExtensionType.User).then(local => { - recommendationsToSuggest = recommendationsToSuggest.filter(id => local.every(local => `${local.manifest.publisher}.${local.manifest.name}` !== id)); + const localExtensions = local.map(e => `${e.manifest.publisher.toLowerCase()}.${e.manifest.name.toLowerCase()}`); + recommendationsToSuggest = recommendationsToSuggest.filter(id => localExtensions.every(local => local !== id.toLowerCase())); if (!recommendationsToSuggest.length) { return; } From 65c3b9e9b83ae05a400f9b7291ac0c040dc3d2fa Mon Sep 17 00:00:00 2001 From: SteVen Batten <6561887+sbatten@users.noreply.github.com> Date: Mon, 25 Jun 2018 12:33:10 -0700 Subject: [PATCH 051/228] Submenus for Menu Widget (#52780) * submenu registration, ignore && mnemonic rendering for now * clean up ActionItems for Menu Widget and submenu * clean up the update actions logic in menubar * fix keyboard navigation for submenus * add terminal menu and update submenu labels * fix missing separator issue * updating some aria labels * bring back action runner to hide menu before running * address feedback from @bpasero --- src/vs/base/browser/contextmenu.ts | 7 +- src/vs/base/browser/dom.ts | 1 + .../base/browser/ui/actionbar/actionbar.css | 2 +- src/vs/base/browser/ui/actionbar/actionbar.ts | 21 +- src/vs/base/browser/ui/menu/menu.css | 11 +- src/vs/base/browser/ui/menu/menu.ts | 181 +++++++++++++++++- .../actions/browser/menuItemActionItem.ts | 6 +- src/vs/platform/actions/common/actions.ts | 46 ++++- src/vs/platform/actions/common/menu.ts | 12 +- .../parts/menubar/menubar.contribution.ts | 123 +++++++++++- .../browser/parts/menubar/menubarPart.ts | 62 +++--- .../electron-browser/media/shell.css | 4 + .../quickopen/browser/commandsHandler.ts | 2 +- .../actions/test/common/menuService.test.ts | 18 +- 14 files changed, 404 insertions(+), 92 deletions(-) diff --git a/src/vs/base/browser/contextmenu.ts b/src/vs/base/browser/contextmenu.ts index 221334278e4..c303d45eeb3 100644 --- a/src/vs/base/browser/contextmenu.ts +++ b/src/vs/base/browser/contextmenu.ts @@ -4,10 +4,11 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { IAction, IActionRunner, Action } from 'vs/base/common/actions'; +import { IAction, IActionRunner } from 'vs/base/common/actions'; import { IActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { TPromise } from 'vs/base/common/winjs.base'; import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; +import { SubmenuAction } from 'vs/base/browser/ui/menu/menu'; export interface IEvent { shiftKey?: boolean; @@ -16,9 +17,9 @@ export interface IEvent { metaKey?: boolean; } -export class ContextSubMenu extends Action { +export class ContextSubMenu extends SubmenuAction { constructor(label: string, public entries: (ContextSubMenu | IAction)[]) { - super('contextsubmenu', label, '', true); + super(label, entries, 'contextsubmenu'); } } diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 3856b7dbeb1..6e7c479e58d 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -819,6 +819,7 @@ export const EventType = { MOUSE_OVER: 'mouseover', MOUSE_MOVE: 'mousemove', MOUSE_OUT: 'mouseout', + MOUSE_LEAVE: 'mouseleave', CONTEXT_MENU: 'contextmenu', WHEEL: 'wheel', // Keyboard diff --git a/src/vs/base/browser/ui/actionbar/actionbar.css b/src/vs/base/browser/ui/actionbar/actionbar.css index c5a2cf6ee23..4ff042d038f 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.css +++ b/src/vs/base/browser/ui/actionbar/actionbar.css @@ -54,7 +54,7 @@ .monaco-action-bar .action-label { font-size: 11px; - margin-right: 4px; + margin-right: 4px; } .monaco-action-bar .action-label.octicon { diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts index a6d79bdbcb0..3fa94537ee5 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -43,8 +43,6 @@ export class BaseActionItem implements IActionItem { public _context: any; public _action: IAction; - static MNEMONIC_REGEX: RegExp = /&&(.)/g; - private _actionRunner: IActionRunner; constructor(context: any, action: IAction, protected options?: IBaseActionItemOptions) { @@ -277,11 +275,7 @@ export class ActionItem extends BaseActionItem { public _updateLabel(): void { if (this.options.label) { - let label = this.getAction().label; - if (label && this.options.isMenu) { - label = label.replace(BaseActionItem.MNEMONIC_REGEX, '$1\u0332'); - } - this.$e.text(label); + this.$e.text(this.getAction().label); } } @@ -565,15 +559,6 @@ export class ActionBar implements IActionRunner { return this.domNode; } - private _addMnemonic(action: IAction, actionItemElement: HTMLElement): void { - let matches = BaseActionItem.MNEMONIC_REGEX.exec(action.label); - if (matches && matches.length === 2) { - let mnemonic = matches[1]; - - actionItemElement.accessKey = mnemonic.toLocaleLowerCase(); - } - } - public push(arg: IAction | IAction[], options: IActionOptions = {}): void { const actions: IAction[] = !Array.isArray(arg) ? [arg] : arg; @@ -591,10 +576,6 @@ export class ActionBar implements IActionRunner { e.stopPropagation(); }); - if (options.isMenu) { - this._addMnemonic(action, actionItemElement); - } - let item: IActionItem = null; if (this.options.actionItemProvider) { diff --git a/src/vs/base/browser/ui/menu/menu.css b/src/vs/base/browser/ui/menu/menu.css index 554b91d60de..f94140de0f2 100644 --- a/src/vs/base/browser/ui/menu/menu.css +++ b/src/vs/base/browser/ui/menu/menu.css @@ -5,6 +5,7 @@ .monaco-menu .monaco-action-bar.vertical { margin-left: 0; + overflow: visible; } .monaco-menu .monaco-action-bar.vertical .actions-container { @@ -47,7 +48,8 @@ background: none; } -.monaco-menu .monaco-action-bar.vertical .keybinding { +.monaco-menu .monaco-action-bar.vertical .keybinding, +.monaco-menu .monaco-action-bar.vertical .submenu-indicator { display: inline-block; -ms-flex: 2 1 auto; flex: 2 1 auto; @@ -57,7 +59,12 @@ text-align: right; } -.monaco-menu .monaco-action-bar.vertical .action-item.disabled .keybinding { +.monaco-menu .monaco-action-bar.vertical .submenu-indicator { + padding: 0.8em .5em; +} + +.monaco-menu .monaco-action-bar.vertical .action-item.disabled .keybinding, +.monaco-menu .monaco-action-bar.vertical .action-item.disabled .submenu-indicator { opacity: 0.4; } diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index 512d107f156..5ea5820cce4 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -7,11 +7,13 @@ import 'vs/css!./menu'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { IActionRunner, IAction } from 'vs/base/common/actions'; -import { ActionBar, IActionItemProvider, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; -import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; +import { IActionRunner, IAction, Action } from 'vs/base/common/actions'; +import { ActionBar, IActionItemProvider, ActionsOrientation, Separator, ActionItem, IActionItemOptions } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ResolvedKeybinding, KeyCode } from 'vs/base/common/keyCodes'; import { Event } from 'vs/base/common/event'; -import { addClass } from 'vs/base/browser/dom'; +import { addClass, EventType, EventHelper, EventLike } from 'vs/base/browser/dom'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { $ } from 'vs/base/browser/builder'; export interface IMenuOptions { context?: any; @@ -21,6 +23,18 @@ export interface IMenuOptions { ariaLabel?: string; } + +export class SubmenuAction extends Action { + constructor(label: string, public entries: (SubmenuAction | IAction)[], cssClass?: string) { + super(!!cssClass ? cssClass : 'submenu', label, '', true); + } +} + +interface ISubMenuData { + parent: Menu; + submenu?: Menu; +} + export class Menu { private actionBar: ActionBar; @@ -35,9 +49,31 @@ export class Menu { menuContainer.setAttribute('role', 'presentation'); container.appendChild(menuContainer); + let parentData: ISubMenuData = { + parent: this + }; + + const getActionItem = (action: IAction) => { + if (action instanceof Separator) { + return new ActionItem(options.context, action, { icon: true }); + } else if (action instanceof SubmenuAction) { + return new SubmenuActionItem(action, action.entries, parentData, options); + } else { + const menuItemOptions: IActionItemOptions = {}; + if (options.getKeyBinding) { + const keybinding = options.getKeyBinding(action); + if (keybinding) { + menuItemOptions.keybinding = keybinding.getLabel(); + } + } + + return new MenuActionItem(options.context, action, menuItemOptions); + } + }; + this.actionBar = new ActionBar(menuContainer, { orientation: ActionsOrientation.VERTICAL, - actionItemProvider: options.actionItemProvider, + actionItemProvider: options.actionItemProvider ? options.actionItemProvider : getActionItem, context: options.context, actionRunner: options.actionRunner, isMenu: true, @@ -70,4 +106,139 @@ export class Menu { this.listener = null; } } +} + +class MenuActionItem extends ActionItem { + static MNEMONIC_REGEX: RegExp = /&&(.)/g; + + constructor(ctx: any, action: IAction, options: IActionItemOptions = {}) { + options.isMenu = true; + super(action, action, options); + } + + private _addMnemonic(action: IAction, actionItemElement: HTMLElement): void { + let matches = MenuActionItem.MNEMONIC_REGEX.exec(action.label); + if (matches && matches.length === 2) { + let mnemonic = matches[1]; + + let ariaLabel = action.label.replace(MenuActionItem.MNEMONIC_REGEX, mnemonic); + + actionItemElement.accessKey = mnemonic.toLocaleLowerCase(); + this.$e.attr('aria-label', ariaLabel); + } else { + this.$e.attr('aria-label', action.label); + } + } + + public render(container: HTMLElement): void { + super.render(container); + + this._addMnemonic(this.getAction(), container); + this.$e.attr('role', 'menuitem'); + } + + public _updateLabel(): void { + if (this.options.label) { + let label = this.getAction().label; + if (label && this.options.isMenu) { + label = label.replace(MenuActionItem.MNEMONIC_REGEX, '$1\u0332'); + } + this.$e.text(label); + } + } +} + +class SubmenuActionItem extends MenuActionItem { + private mysubmenu: Menu; + + constructor( + action: IAction, + private submenuActions: IAction[], + private parentData: ISubMenuData, + private submenuOptions?: IMenuOptions + ) { + super(action, action, { label: true, isMenu: true }); + } + + public render(container: HTMLElement): void { + super.render(container); + + this.builder = $(container); + $(this.builder).addClass('monaco-submenu-item'); + $('span.submenu-indicator').text('\u25B6').appendTo(this.builder); + this.$e.attr('role', 'menu'); + + $(this.builder).on(EventType.KEY_UP, (e) => { + let event = new StandardKeyboardEvent(e as KeyboardEvent); + if (event.equals(KeyCode.RightArrow)) { + EventHelper.stop(e, true); + + this.createSubmenu(); + } + }); + + $(this.builder).on(EventType.KEY_DOWN, (e) => { + let event = new StandardKeyboardEvent(e as KeyboardEvent); + if (event.equals(KeyCode.RightArrow)) { + EventHelper.stop(e, true); + } + }); + + $(this.builder).on(EventType.MOUSE_OVER, (e) => { + this.cleanupExistingSubmenu(false); + this.createSubmenu(); + }); + + + $(this.builder).on(EventType.MOUSE_LEAVE, (e) => { + this.parentData.parent.focus(); + this.cleanupExistingSubmenu(true); + }); + } + + public onClick(e: EventLike) { + // stop clicking from trying to run an action + EventHelper.stop(e, true); + } + + private cleanupExistingSubmenu(force: boolean) { + if (this.parentData.submenu && (force || (this.parentData.submenu !== this.mysubmenu))) { + this.parentData.submenu.dispose(); + this.parentData.submenu = null; + } + } + + private createSubmenu() { + if (!this.parentData.submenu) { + const submenuContainer = $(this.builder).div({ class: 'monaco-submenu menubar-menu-items-holder context-view' }); + + $(submenuContainer).style({ + 'left': `${$(this.builder).getClientArea().width}px` + }); + + $(submenuContainer).on(EventType.KEY_UP, (e) => { + let event = new StandardKeyboardEvent(e as KeyboardEvent); + if (event.equals(KeyCode.LeftArrow)) { + EventHelper.stop(e, true); + + this.parentData.parent.focus(); + this.parentData.submenu.dispose(); + this.parentData.submenu = null; + } + }); + + $(submenuContainer).on(EventType.KEY_DOWN, (e) => { + let event = new StandardKeyboardEvent(e as KeyboardEvent); + if (event.equals(KeyCode.LeftArrow)) { + EventHelper.stop(e, true); + } + }); + + + this.parentData.submenu = new Menu(submenuContainer.getHTMLElement(), this.submenuActions, this.submenuOptions); + this.parentData.submenu.focus(); + + this.mysubmenu = this.parentData.submenu; + } + } } \ No newline at end of file diff --git a/src/vs/platform/actions/browser/menuItemActionItem.ts b/src/vs/platform/actions/browser/menuItemActionItem.ts index 1c7baf46a77..a8ff208c0a6 100644 --- a/src/vs/platform/actions/browser/menuItemActionItem.ts +++ b/src/vs/platform/actions/browser/menuItemActionItem.ts @@ -7,7 +7,7 @@ import { localize } from 'vs/nls'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IMenu, MenuItemAction, IMenuActionOptions, ICommandAction } from 'vs/platform/actions/common/actions'; +import { IMenu, MenuItemAction, IMenuActionOptions, ICommandAction, SubmenuItemAction } from 'vs/platform/actions/common/actions'; import { IAction } from 'vs/base/common/actions'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { ActionItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; @@ -91,11 +91,11 @@ export function fillInActionBarActions(menu: IMenu, options: IMenuActionOptions, fillInActions(groups, target, false, isPrimaryGroup); } -function fillInActions(groups: [string, MenuItemAction[]][], target: IAction[] | { primary: IAction[]; secondary: IAction[]; }, getAlternativeActions, isPrimaryGroup: (group: string) => boolean = group => group === 'navigation'): void { +function fillInActions(groups: [string, (MenuItemAction | SubmenuItemAction)[]][], target: IAction[] | { primary: IAction[]; secondary: IAction[]; }, getAlternativeActions, isPrimaryGroup: (group: string) => boolean = group => group === 'navigation'): void { for (let tuple of groups) { let [group, actions] = tuple; if (getAlternativeActions) { - actions = actions.map(a => !!a.alt ? a.alt : a); + actions = actions.map(a => (a instanceof MenuItemAction) && !!a.alt ? a.alt : a); } if (isPrimaryGroup(group)) { diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 6e58a2d23df..c4b1e0f958c 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -43,6 +43,22 @@ export interface IMenuItem { order?: number; } +export interface ISubmenuItem { + title: string | ILocalizedString; + submenu: MenuId; + when?: ContextKeyExpr; + group?: 'navigation' | string; + order?: number; +} + +export function isIMenuItem(item: IMenuItem | ISubmenuItem): item is IMenuItem { + return (item as IMenuItem).command !== undefined; +} + +export function isISubmenuItem(item: IMenuItem | ISubmenuItem): item is ISubmenuItem { + return (item as ISubmenuItem).submenu !== undefined; +} + export class MenuId { private static ID = 1; @@ -81,6 +97,7 @@ export class MenuId { static readonly MenubarWindowMenu = new MenuId(); static readonly MenubarPreferencesMenu = new MenuId(); static readonly MenubarHelpMenu = new MenuId(); + static readonly MenubarTerminalMenu = new MenuId(); readonly id: string = String(MenuId.ID++); } @@ -92,7 +109,7 @@ export interface IMenuActionOptions { export interface IMenu extends IDisposable { onDidChange: Event; - getActions(options?: IMenuActionOptions): [string, MenuItemAction[]][]; + getActions(options?: IMenuActionOptions): [string, (MenuItemAction | SubmenuItemAction)[]][]; } export const IMenuService = createDecorator('menuService'); @@ -107,15 +124,15 @@ export interface IMenuService { export interface IMenuRegistry { addCommand(userCommand: ICommandAction): boolean; getCommand(id: string): ICommandAction; - appendMenuItem(menu: MenuId, item: IMenuItem): IDisposable; - getMenuItems(loc: MenuId): IMenuItem[]; + appendMenuItem(menu: MenuId, item: IMenuItem | ISubmenuItem): IDisposable; + getMenuItems(loc: MenuId): (IMenuItem | ISubmenuItem)[]; } export const MenuRegistry: IMenuRegistry = new class implements IMenuRegistry { private _commands: { [id: string]: ICommandAction } = Object.create(null); - private _menuItems: { [loc: string]: IMenuItem[] } = Object.create(null); + private _menuItems: { [loc: string]: (IMenuItem | ISubmenuItem)[] } = Object.create(null); addCommand(command: ICommandAction): boolean { const old = this._commands[command.id]; @@ -127,7 +144,7 @@ export const MenuRegistry: IMenuRegistry = new class implements IMenuRegistry { return this._commands[id]; } - appendMenuItem({ id }: MenuId, item: IMenuItem): IDisposable { + appendMenuItem({ id }: MenuId, item: IMenuItem | ISubmenuItem): IDisposable { let array = this._menuItems[id]; if (!array) { this._menuItems[id] = array = [item]; @@ -144,7 +161,7 @@ export const MenuRegistry: IMenuRegistry = new class implements IMenuRegistry { }; } - getMenuItems({ id }: MenuId): IMenuItem[] { + getMenuItems({ id }: MenuId): (IMenuItem | ISubmenuItem)[] { const result = this._menuItems[id] || []; if (id === MenuId.CommandPalette.id) { @@ -155,9 +172,12 @@ export const MenuRegistry: IMenuRegistry = new class implements IMenuRegistry { return result; } - private _appendImplicitItems(result: IMenuItem[]) { + private _appendImplicitItems(result: (IMenuItem | ISubmenuItem)[]) { const set = new Set(); - for (const { command, alt } of result) { + + const temp = result.filter(item => { return isIMenuItem(item); }) as IMenuItem[]; + + for (const { command, alt } of temp) { set.add(command.id); if (alt) { set.add(alt.id); @@ -186,6 +206,16 @@ export class ExecuteCommandAction extends Action { } } +export class SubmenuItemAction extends Action { + // private _options: IMenuActionOptions; + + readonly item: ISubmenuItem; + constructor(item: ISubmenuItem) { + typeof item.title === 'string' ? super('', item.title, 'submenu') : super('', item.title.value, 'submenu'); + this.item = item; + } +} + export class MenuItemAction extends ExecuteCommandAction { private _options: IMenuActionOptions; diff --git a/src/vs/platform/actions/common/menu.ts b/src/vs/platform/actions/common/menu.ts index e3ad0820736..3d89d746fe4 100644 --- a/src/vs/platform/actions/common/menu.ts +++ b/src/vs/platform/actions/common/menu.ts @@ -9,10 +9,10 @@ import { Event, Emitter } from 'vs/base/common/event'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { TPromise } from 'vs/base/common/winjs.base'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { MenuId, MenuRegistry, MenuItemAction, IMenu, IMenuItem, IMenuActionOptions } from 'vs/platform/actions/common/actions'; +import { MenuId, MenuRegistry, MenuItemAction, IMenu, IMenuItem, IMenuActionOptions, ISubmenuItem, SubmenuItemAction, isIMenuItem } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; -type MenuItemGroup = [string, IMenuItem[]]; +type MenuItemGroup = [string, (IMenuItem | ISubmenuItem)[]]; export class Menu implements IMenu { @@ -66,14 +66,14 @@ export class Menu implements IMenu { return this._onDidChange.event; } - getActions(options: IMenuActionOptions): [string, MenuItemAction[]][] { - const result: [string, MenuItemAction[]][] = []; + getActions(options: IMenuActionOptions): [string, (MenuItemAction | SubmenuItemAction)[]][] { + const result: [string, (MenuItemAction | SubmenuItemAction)[]][] = []; for (let group of this._menuGroups) { const [id, items] = group; - const activeActions: MenuItemAction[] = []; + const activeActions: (MenuItemAction | SubmenuItemAction)[] = []; for (const item of items) { if (this._contextKeyService.contextMatchesRules(item.when)) { - const action = new MenuItemAction(item.command, item.alt, options, this._contextKeyService, this._commandService); + const action = isIMenuItem(item) ? new MenuItemAction(item.command, item.alt, options, this._contextKeyService, this._commandService) : new SubmenuItemAction(item); action.order = item.order; //TODO@Ben order is menu item property, not an action property activeActions.push(action); } diff --git a/src/vs/workbench/browser/parts/menubar/menubar.contribution.ts b/src/vs/workbench/browser/parts/menubar/menubar.contribution.ts index b551e1158cb..a156d5fcc77 100644 --- a/src/vs/workbench/browser/parts/menubar/menubar.contribution.ts +++ b/src/vs/workbench/browser/parts/menubar/menubar.contribution.ts @@ -10,15 +10,16 @@ import { isMacintosh } from 'vs/base/common/platform'; // TODO: Add submenu support to remove layout, preferences, and recent top level menubarCommands.setup(); +recentMenuRegistration(); fileMenuRegistration(); editMenuRegistration(); -recentMenuRegistration(); selectionMenuRegistration(); viewMenuRegistration(); layoutMenuRegistration(); goMenuRegistration(); debugMenuRegistration(); tasksMenuRegistration(); +terminalMenuRegistration(); if (isMacintosh) { windowMenuRegistration(); @@ -74,6 +75,13 @@ function fileMenuRegistration() { order: 3 }); + MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + title: nls.localize({ key: 'miOpenRecent', comment: ['&& denotes a mnemonic'] }, "Open &&Recent"), + submenu: MenuId.MenubarRecentMenu, + group: '2_open', + order: 4 + }); + MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { group: '3_workspace', command: { @@ -128,6 +136,13 @@ function fileMenuRegistration() { order: 1 }); + MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + title: nls.localize({ key: 'miPreferences', comment: ['&& denotes a mnemonic'] }, "&&Preferences"), + submenu: MenuId.MenubarPreferencesMenu, + group: '5_autosave', + order: 2 + }); + MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { group: '6_close', command: { @@ -603,6 +618,13 @@ function viewMenuRegistration() { }); // TODO: Editor Layout Submenu + MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { + title: nls.localize({ key: 'miEditorLayout', comment: ['&& denotes a mnemonic'] }, "Editor &&Layout"), + submenu: MenuId.MenubarLayoutMenu, + group: '5_layout', + order: 1 + }); + // Workbench Layout MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { @@ -1475,3 +1497,102 @@ function helpMenuRegistration() { order: 1 }); } + +function terminalMenuRegistration() { + + // Manage + + MenuRegistry.appendMenuItem(MenuId.MenubarTerminalMenu, { + group: '1_manage', + command: { + id: 'workbench.action.terminal.new', + title: nls.localize({ key: 'miNewTerminal', comment: ['&& denotes a mnemonic'] }, "&&New Terminal") + }, + order: 1 + }); + + MenuRegistry.appendMenuItem(MenuId.MenubarTerminalMenu, { + group: '1_manage', + command: { + id: 'workbench.action.terminal.split', + title: nls.localize({ key: 'miSplitTerminal', comment: ['&& denotes a mnemonic'] }, "&&Split Terminal") + }, + order: 2 + }); + + MenuRegistry.appendMenuItem(MenuId.MenubarTerminalMenu, { + group: '1_manage', + command: { + id: 'workbench.action.terminal.kill', + title: nls.localize({ key: 'miKillTerminal', comment: ['&& denotes a mnemonic'] }, "&&Kill Terminal") + }, + order: 3 + }); + + // Run + + MenuRegistry.appendMenuItem(MenuId.MenubarTerminalMenu, { + group: '2_run', + command: { + id: 'workbench.action.terminal.clear', + title: nls.localize({ key: 'miClear', comment: ['&& denotes a mnemonic'] }, "&&Clear") + }, + order: 1 + }); + + MenuRegistry.appendMenuItem(MenuId.MenubarTerminalMenu, { + group: '2_run', + command: { + id: 'workbench.action.terminal.runActiveFile', + title: nls.localize({ key: 'miRunActiveFile', comment: ['&& denotes a mnemonic'] }, "Run &&Active File") + }, + order: 2 + }); + + MenuRegistry.appendMenuItem(MenuId.MenubarTerminalMenu, { + group: '2_run', + command: { + id: 'workbench.action.terminal.runSelectedFile', + title: nls.localize({ key: 'miRunSelectedText', comment: ['&& denotes a mnemonic'] }, "Run &&Selected Text") + }, + order: 3 + }); + + // Selection + + MenuRegistry.appendMenuItem(MenuId.MenubarTerminalMenu, { + group: '3_selection', + command: { + id: 'workbench.action.terminal.scrollToPreviousCommand', + title: nls.localize({ key: 'miScrollToPreviousCommand', comment: ['&& denotes a mnemonic'] }, "Scroll To Previous Command") + }, + order: 1 + }); + + MenuRegistry.appendMenuItem(MenuId.MenubarTerminalMenu, { + group: '3_selection', + command: { + id: 'workbench.action.terminal.scrollToNextCommand', + title: nls.localize({ key: 'miScrollToNextCommand', comment: ['&& denotes a mnemonic'] }, "Scroll To Next Command") + }, + order: 2 + }); + + MenuRegistry.appendMenuItem(MenuId.MenubarTerminalMenu, { + group: '3_selection', + command: { + id: 'workbench.action.terminal.selectToPreviousCommand', + title: nls.localize({ key: 'miSelectToPreviousCommand', comment: ['&& denotes a mnemonic'] }, "Select To Previous Command") + }, + order: 3 + }); + + MenuRegistry.appendMenuItem(MenuId.MenubarTerminalMenu, { + group: '3_selection', + command: { + id: 'workbench.action.terminal.selectToNextCommand', + title: nls.localize({ key: 'miSelectToNextCommand', comment: ['&& denotes a mnemonic'] }, "Select To Next Command") + }, + order: 4 + }); +} \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/menubar/menubarPart.ts b/src/vs/workbench/browser/parts/menubar/menubarPart.ts index 6ecc50082dc..ab49bdcac26 100644 --- a/src/vs/workbench/browser/parts/menubar/menubarPart.ts +++ b/src/vs/workbench/browser/parts/menubar/menubarPart.ts @@ -11,18 +11,18 @@ import * as nls from 'vs/nls'; import * as browser from 'vs/base/browser/browser'; import { Part } from 'vs/workbench/browser/part'; import { IMenubarService, IMenubarMenu, IMenubarMenuItemAction, IMenubarData } from 'vs/platform/menubar/common/menubar'; -import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions'; +import { IMenuService, MenuId, IMenu, SubmenuItemAction } from 'vs/platform/actions/common/actions'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IWindowService, MenuBarVisibility } from 'vs/platform/windows/common/windows'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ActionRunner, IActionRunner, IAction } from 'vs/base/common/actions'; import { Builder, $ } from 'vs/base/browser/builder'; -import { Separator, ActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { EventType, Dimension, toggleClass } from 'vs/base/browser/dom'; import { TITLE_BAR_ACTIVE_BACKGROUND, TITLE_BAR_ACTIVE_FOREGROUND } from 'vs/workbench/common/theme'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { isWindows, isMacintosh } from 'vs/base/common/platform'; -import { Menu, IMenuOptions } from 'vs/base/browser/ui/menu/menu'; +import { Menu, IMenuOptions, SubmenuAction } from 'vs/base/browser/ui/menu/menu'; import { KeyCode } from 'vs/base/common/keyCodes'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; @@ -53,15 +53,13 @@ export class MenubarPart extends Part { private topLevelMenus: { 'File': IMenu; 'Edit': IMenu; - 'Recent': IMenu; 'Selection': IMenu; 'View': IMenu; - 'Layout': IMenu; 'Go': IMenu; + 'Terminal': IMenu; 'Debug': IMenu; 'Tasks': IMenu; 'Window'?: IMenu; - 'Preferences': IMenu; 'Help': IMenu; [index: string]: IMenu; }; @@ -69,14 +67,12 @@ export class MenubarPart extends Part { private topLevelTitles = { 'File': nls.localize({ key: 'mFile', comment: ['&& denotes a mnemonic'] }, "&&File"), 'Edit': nls.localize({ key: 'mEdit', comment: ['&& denotes a mnemonic'] }, "&&Edit"), - 'Recent': nls.localize({ key: 'mRecent', comment: ['&& denotes a mnemonic'] }, "&&Recent"), 'Selection': nls.localize({ key: 'mSelection', comment: ['&& denotes a mnemonic'] }, "&&Selection"), 'View': nls.localize({ key: 'mView', comment: ['&& denotes a mnemonic'] }, "&&View"), - 'Layout': nls.localize({ key: 'mLayout', comment: ['&& denotes a mnemonic'] }, "&&Layout"), 'Go': nls.localize({ key: 'mGoto', comment: ['&& denotes a mnemonic'] }, "&&Go"), + 'Terminal': nls.localize({ key: 'mTerminal', comment: ['&& denotes a mnemonic'] }, "&&Terminal"), 'Debug': nls.localize({ key: 'mDebug', comment: ['&& denotes a mnemonic'] }, "&&Debug"), 'Tasks': nls.localize({ key: 'mTasks', comment: ['&& denotes a mnemonic'] }, "&&Tasks"), - 'Preferences': nls.localize({ key: 'mPreferences', comment: ['&& denotes a mnemonic'] }, "&&Preferences"), 'Help': nls.localize({ key: 'mHelp', comment: ['&& denotes a mnemonic'] }, "&&Help") }; @@ -120,14 +116,12 @@ export class MenubarPart extends Part { this.topLevelMenus = { 'File': this.menuService.createMenu(MenuId.MenubarFileMenu, this.contextKeyService), 'Edit': this.menuService.createMenu(MenuId.MenubarEditMenu, this.contextKeyService), - 'Recent': this.menuService.createMenu(MenuId.MenubarRecentMenu, this.contextKeyService), 'Selection': this.menuService.createMenu(MenuId.MenubarSelectionMenu, this.contextKeyService), 'View': this.menuService.createMenu(MenuId.MenubarViewMenu, this.contextKeyService), - 'Layout': this.menuService.createMenu(MenuId.MenubarLayoutMenu, this.contextKeyService), 'Go': this.menuService.createMenu(MenuId.MenubarGoMenu, this.contextKeyService), + 'Terminal': this.menuService.createMenu(MenuId.MenubarTerminalMenu, this.contextKeyService), 'Debug': this.menuService.createMenu(MenuId.MenubarDebugMenu, this.contextKeyService), 'Tasks': this.menuService.createMenu(MenuId.MenubarTasksMenu, this.contextKeyService), - 'Preferences': this.menuService.createMenu(MenuId.MenubarPreferencesMenu, this.contextKeyService), 'Help': this.menuService.createMenu(MenuId.MenubarHelpMenu, this.contextKeyService) }; @@ -378,27 +372,34 @@ export class MenubarPart extends Part { titleElement: titleElement }); - // Update cached actions array for CustomMenus - const updateActions = () => { - this.customMenus[menuIndex].actions = []; + const updateActions = (menu: IMenu, target: IAction[]) => { + target.splice(0); let groups = menu.getActions(); for (let group of groups) { const [, actions] = group; - actions.map((action: IAction) => { - action.label = this.calculateActionLabel(action); - this.setCheckedStatus(action); - }); + for (let action of actions) { + if (action instanceof SubmenuItemAction) { + const submenu = this.menuService.createMenu(action.item.submenu, this.contextKeyService); + const submenuActions = []; + updateActions(submenu, submenuActions); + target.push(new SubmenuAction(action.label, submenuActions)); + } else { + action.label = this.calculateActionLabel(action); + this.setCheckedStatus(action); + target.push(action); + } + } - this.customMenus[menuIndex].actions.push(...actions); - this.customMenus[menuIndex].actions.push(new Separator()); + target.push(new Separator()); } - this.customMenus[menuIndex].actions.pop(); + target.pop(); }; - menu.onDidChange(updateActions); - updateActions(); + this.customMenus[menuIndex].actions = []; + menu.onDidChange(() => updateActions(menu, this.customMenus[menuIndex].actions)); + updateActions(menu, this.customMenus[menuIndex].actions); this.customMenus[menuIndex].titleElement.on(EventType.CLICK, (event) => { this.toggleCustomMenu(menuIndex); @@ -537,14 +538,6 @@ export class MenubarPart extends Part { this.toggleCustomMenu(0); } - private _getActionItem(action: IAction): ActionItem { - const keybinding = this.keybindingService.lookupKeybinding(action.id); - if (keybinding) { - return new ActionItem(action, action, { label: true, keybinding: keybinding.getLabel(), isMenu: true }); - } - return null; - } - private toggleCustomMenu(menuIndex: number): void { const customMenu = this.customMenus[menuIndex]; @@ -573,9 +566,10 @@ export class MenubarPart extends Part { }); let menuOptions: IMenuOptions = { + getKeyBinding: (action) => this.keybindingService.lookupKeybinding(action.id), actionRunner: this.actionRunner, - ariaLabel: 'File', - actionItemProvider: (action) => { return this._getActionItem(action); } + // ariaLabel: 'File' + // actionItemProvider: (action) => { return this._getActionItem(action); } }; let menuWidget = new Menu(menuHolder.getHTMLElement(), customMenu.actions, menuOptions); diff --git a/src/vs/workbench/electron-browser/media/shell.css b/src/vs/workbench/electron-browser/media/shell.css index 6748e7384c3..436641f4237 100644 --- a/src/vs/workbench/electron-browser/media/shell.css +++ b/src/vs/workbench/electron-browser/media/shell.css @@ -74,6 +74,10 @@ padding: 0.5em 2em; } +.monaco-shell .monaco-menu .monaco-action-bar.vertical .submenu-indicator { + padding: 0.5em 1em; +} + /* START Keyboard Focus Indication Styles */ .monaco-shell [tabindex="0"]:focus, diff --git a/src/vs/workbench/parts/quickopen/browser/commandsHandler.ts b/src/vs/workbench/parts/quickopen/browser/commandsHandler.ts index 0e1dd8af193..c978a3b0cc7 100644 --- a/src/vs/workbench/parts/quickopen/browser/commandsHandler.ts +++ b/src/vs/workbench/parts/quickopen/browser/commandsHandler.ts @@ -412,7 +412,7 @@ export class CommandsHandler extends QuickOpenHandler { // Other Actions const menu = this.editorService.invokeWithinEditorContext(accessor => this.menuService.createMenu(MenuId.CommandPalette, accessor.get(IContextKeyService))); - const menuActions = menu.getActions().reduce((r, [, actions]) => [...r, ...actions], []); + const menuActions = menu.getActions().reduce((r, [, actions]) => [...r, ...actions], []).filter(action => action instanceof MenuItemAction) as MenuItemAction[]; const commandEntries = this.menuItemActionsToEntries(menuActions, searchValue); // Concat diff --git a/src/vs/workbench/services/actions/test/common/menuService.test.ts b/src/vs/workbench/services/actions/test/common/menuService.test.ts index 61937dd287f..c986b7bdfac 100644 --- a/src/vs/workbench/services/actions/test/common/menuService.test.ts +++ b/src/vs/workbench/services/actions/test/common/menuService.test.ts @@ -5,7 +5,7 @@ 'use strict'; import * as assert from 'assert'; -import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; +import { MenuRegistry, MenuId, isIMenuItem } from 'vs/platform/actions/common/actions'; import { MenuService } from 'vs/workbench/services/actions/common/menuService'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { NullCommandService } from 'vs/platform/commands/common/commands'; @@ -190,13 +190,15 @@ suite('MenuService', function () { let foundA = false; let foundB = false; for (const item of MenuRegistry.getMenuItems(MenuId.CommandPalette)) { - if (item.command.id === 'a') { - assert.equal(item.command.title, 'Explicit'); - foundA = true; - } - if (item.command.id === 'b') { - assert.equal(item.command.title, 'Implicit'); - foundB = true; + if (isIMenuItem(item)) { + if (item.command.id === 'a') { + assert.equal(item.command.title, 'Explicit'); + foundA = true; + } + if (item.command.id === 'b') { + assert.equal(item.command.title, 'Implicit'); + foundB = true; + } } } assert.equal(foundA, true); From d88b3dd55be626154bf1d43c6d8570e02b4a32bb Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Mon, 25 Jun 2018 12:46:03 -0700 Subject: [PATCH 052/228] UX Changes to ignore recommendations (#52514) * UX Changes to ignore recommendations * Fix missed negation when changing to shouldRecommend * Update restored recommendation wording * Update wording again * Remove unused field --- .../common/extensionManagement.ts | 8 +-- .../electron-browser/extensionEditor.ts | 25 +++++-- .../electron-browser/extensionTipsService.ts | 65 +++++++++++-------- .../electron-browser/extensionsActions.ts | 36 +++++++++- .../electron-browser/extensionsList.ts | 2 +- .../media/extensionActions.css | 21 ------ .../media/extensionEditor.css | 51 +++++++++++---- .../extensionsTipsService.test.ts | 48 ++++++++------ 8 files changed, 156 insertions(+), 100 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index fe5cf912735..3257a0ba2b4 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -378,11 +378,6 @@ export interface IExtensionEnablementService { setEnablement(extension: ILocalExtension, state: EnablementState): TPromise; } -export interface IIgnoredRecommendations { - global: string[]; - workspace: string[]; -} - export interface IExtensionsConfigContent { recommendations: string[]; unwantedRecommendations: string[]; @@ -416,8 +411,7 @@ export interface IExtensionTipsService { getAllRecommendations(): TPromise; getKeywordsForExtension(extension: string): string[]; getRecommendationsForExtension(extension: string): string[]; - getAllIgnoredRecommendations(): IIgnoredRecommendations; - ignoreExtensionRecommendation(extensionId: string): void; + toggleIgnoredRecommendation(extensionId: string, shouldIgnore: boolean): void; onRecommendationChange: Event; } diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts index 966191a9793..3018ddd098f 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts @@ -31,7 +31,7 @@ import { Renderer, DataSource, Controller } from 'vs/workbench/parts/extensions/ import { RatingsWidget, InstallCountWidget } from 'vs/workbench/parts/extensions/browser/extensionsWidgets'; import { EditorOptions } from 'vs/workbench/common/editor'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; -import { CombinedInstallAction, UpdateAction, EnableAction, DisableAction, ReloadAction, MaliciousStatusLabelAction, DisabledStatusLabelAction, MultiServerInstallAction, MultiServerUpdateAction, IgnoreExtensionRecommendationAction } from 'vs/workbench/parts/extensions/electron-browser/extensionsActions'; +import { CombinedInstallAction, UpdateAction, EnableAction, DisableAction, ReloadAction, MaliciousStatusLabelAction, DisabledStatusLabelAction, MultiServerInstallAction, MultiServerUpdateAction, IgnoreExtensionRecommendationAction, UndoIgnoreExtensionRecommendationAction } from 'vs/workbench/parts/extensions/electron-browser/extensionsActions'; import { WebviewElement } from 'vs/workbench/parts/webview/electron-browser/webviewElement'; import { KeybindingIO } from 'vs/workbench/services/keybinding/common/keybindingIO'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -389,19 +389,30 @@ export class ExtensionEditor extends BaseEditor { this.transientDisposables.push(enableAction, updateAction, reloadAction, disableAction, installAction, maliciousStatusAction, disabledStatusAction); const ignoreAction = this.instantiationService.createInstance(IgnoreExtensionRecommendationAction); + const undoIgnoreAction = this.instantiationService.createInstance(UndoIgnoreExtensionRecommendationAction); ignoreAction.extension = extension; + undoIgnoreAction.extension = extension; this.extensionTipsService.onRecommendationChange(change => { - if (change.extensionId.toLowerCase() === extension.id.toLowerCase() && change.isRecommended === false) { - addClass(this.header, 'recommendation-ignored'); - removeClass(this.header, 'recommended'); - this.recommendationText.textContent = localize('recommendationHasBeenIgnored', "You have chosen not to receive recommendations for this extension."); + if (change.extensionId.toLowerCase() === extension.id.toLowerCase()) { + if (change.isRecommended) { + const extRecommendations = this.extensionTipsService.getAllRecommendationsWithReason(); + if (extRecommendations[extension.id.toLowerCase()]) { + removeClass(this.header, 'recommendation-ignored'); + addClass(this.header, 'recommended'); + this.recommendationText.textContent = extRecommendations[extension.id.toLowerCase()].reasonText; + } + } else { + addClass(this.header, 'recommendation-ignored'); + removeClass(this.header, 'recommended'); + this.recommendationText.textContent = localize('recommendationHasBeenIgnored', "You have chosen not to receive recommendations for this extension."); + } } }); this.ignoreActionbar.clear(); - this.ignoreActionbar.push([ignoreAction], { icon: true, label: true }); - this.transientDisposables.push(ignoreAction); + this.ignoreActionbar.push([ignoreAction, undoIgnoreAction], { icon: true, label: true }); + this.transientDisposables.push(ignoreAction, undoIgnoreAction); this.content.innerHTML = ''; // Clear content before setting navbar actions. diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts index 20ed595d6b6..2a77811139a 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts @@ -10,7 +10,10 @@ import { forEach } from 'vs/base/common/collections'; import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; import { match } from 'vs/base/common/glob'; import * as json from 'vs/base/common/json'; -import { IExtensionManagementService, IExtensionGalleryService, IExtensionTipsService, ExtensionRecommendationReason, LocalExtensionType, EXTENSION_IDENTIFIER_PATTERN, IIgnoredRecommendations, IExtensionsConfigContent, RecommendationChangeNotification, IExtensionRecommendation, ExtensionRecommendationSource, IExtensionManagementServerService, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { + IExtensionManagementService, IExtensionGalleryService, IExtensionTipsService, ExtensionRecommendationReason, LocalExtensionType, EXTENSION_IDENTIFIER_PATTERN, + IExtensionsConfigContent, RecommendationChangeNotification, IExtensionRecommendation, ExtensionRecommendationSource, IExtensionManagementServerService, InstallOperation +} from 'vs/platform/extensionManagement/common/extensionManagement'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ITextModel } from 'vs/editor/common/model'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; @@ -82,6 +85,8 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe private readonly _onRecommendationChange: Emitter = new Emitter(); onRecommendationChange: Event = this._onRecommendationChange.event; + private _sessionIgnoredRecommendations: { [id: string]: { reasonId: ExtensionRecommendationReason } } = {}; + private _sessionRestoredRecommendations: { [id: string]: { reasonId: ExtensionRecommendationReason } } = {}; constructor( @IExtensionGalleryService private readonly _galleryService: IExtensionGalleryService, @@ -206,6 +211,11 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe reasonText: localize('workspaceRecommendation', "This extension is recommended by users of the current workspace.") }); + Object.keys(this._sessionRestoredRecommendations).forEach(x => output[x.toLowerCase()] = { + reasonId: this._sessionRestoredRecommendations[x].reasonId, + reasonText: localize('restoredRecommendation', "You will receive recommendations for this extension in your next VS Code session.") + }); + return output; } @@ -260,7 +270,6 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe this._allIgnoredRecommendations = distinct([...this._globallyIgnoredRecommendations, ...this._workspaceIgnoredRecommendations]); this.refilterAllRecommendations(); - })); } @@ -358,13 +367,6 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe } } - getAllIgnoredRecommendations(): IIgnoredRecommendations { - return { - workspace: this._workspaceIgnoredRecommendations, - global: this._globallyIgnoredRecommendations - }; - } - private isExtensionAllowedToBeRecommended(id: string): boolean { return this._allIgnoredRecommendations.indexOf(id.toLowerCase()) === -1; } @@ -395,13 +397,14 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe } } return this._fileBasedRecommendations[a].recommendedTime > this._fileBasedRecommendations[b].recommendedTime ? -1 : 1; - }) - .map(extensionId => ({ extensionId, sources: this._fileBasedRecommendations[extensionId].sources })); + }).map(extensionId => ({ extensionId, sources: this._fileBasedRecommendations[extensionId].sources })); } getOtherRecommendations(): TPromise { return this.fetchProactiveRecommendations().then(() => { - const others = distinct([...Object.keys(this._exeBasedRecommendations), ...this._dynamicWorkspaceRecommendations]); + const others = distinct([ + ...Object.keys(this._exeBasedRecommendations), + ...this._dynamicWorkspaceRecommendations]); shuffle(others); return others.map(extensionId => { const sources: ExtensionRecommendationSource[] = []; @@ -960,29 +963,35 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe return Object.keys(result); } - ignoreExtensionRecommendation(extensionId: string): void { - /* __GDPR__ - "extensionsRecommendations:ignoreRecommendation" : { - "recommendationReason": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } + toggleIgnoredRecommendation(extensionId: string, shouldIgnore: boolean): void { + const lowerId = extensionId.toLowerCase(); + if (shouldIgnore) { + const reason = this.getAllRecommendationsWithReason()[lowerId]; + if (reason && reason.reasonId) { + /* __GDPR__ + "extensionsRecommendations:ignoreRecommendation" : { + "recommendationReason": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } + } + */ + this.telemetryService.publicLog('extensionsRecommendations:ignoreRecommendation', { id: extensionId, recommendationReason: reason.reasonId }); + } + this._sessionIgnoredRecommendations[lowerId] = reason; + delete this._sessionRestoredRecommendations[lowerId]; + this._globallyIgnoredRecommendations = distinct([...this._globallyIgnoredRecommendations, lowerId].map(id => id.toLowerCase())); + } else { + this._globallyIgnoredRecommendations = this._globallyIgnoredRecommendations.filter(id => id !== lowerId); + if (this._sessionIgnoredRecommendations[lowerId]) { + this._sessionRestoredRecommendations[lowerId] = this._sessionIgnoredRecommendations[lowerId]; + delete this._sessionIgnoredRecommendations[lowerId]; } - */ - const reason = this.getAllRecommendationsWithReason()[extensionId.toLowerCase()]; - if (reason && reason.reasonId) { - this.telemetryService.publicLog('extensionsRecommendations:ignoreRecommendation', { id: extensionId, recommendationReason: reason.reasonId }); } - - - this._globallyIgnoredRecommendations = distinct( - [...JSON.parse(this.storageService.get('extensionsAssistant/ignored_recommendations', StorageScope.GLOBAL, '[]')), extensionId.toLowerCase()] - .map(id => id.toLowerCase())); - this.storageService.store('extensionsAssistant/ignored_recommendations', JSON.stringify(this._globallyIgnoredRecommendations), StorageScope.GLOBAL); this._allIgnoredRecommendations = distinct([...this._globallyIgnoredRecommendations, ...this._workspaceIgnoredRecommendations]); this.refilterAllRecommendations(); - this._onRecommendationChange.fire({ extensionId: extensionId, isRecommended: false }); + this._onRecommendationChange.fire({ extensionId: extensionId, isRecommended: !shouldIgnore }); } dispose() { diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts index 0ef379a0938..4343a09af02 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts @@ -1699,7 +1699,7 @@ export class IgnoreExtensionRecommendationAction extends Action { static readonly ID = 'extensions.ignore'; - private static readonly Class = 'extension-action ignore octicon octicon-x'; + private static readonly Class = 'extension-action ignore'; private disposables: IDisposable[] = []; extension: IExtension; @@ -1707,7 +1707,7 @@ export class IgnoreExtensionRecommendationAction extends Action { constructor( @IExtensionTipsService private extensionsTipsService: IExtensionTipsService, ) { - super(IgnoreExtensionRecommendationAction.ID); + super(IgnoreExtensionRecommendationAction.ID, 'Ignore Recommendation'); this.class = IgnoreExtensionRecommendationAction.Class; this.tooltip = localize('ignoreExtensionRecommendation', "Do not recommend this extension again"); @@ -1715,7 +1715,37 @@ export class IgnoreExtensionRecommendationAction extends Action { } public run(): TPromise { - this.extensionsTipsService.ignoreExtensionRecommendation(this.extension.id); + this.extensionsTipsService.toggleIgnoredRecommendation(this.extension.id, true); + return TPromise.as(null); + } + + dispose(): void { + super.dispose(); + this.disposables = dispose(this.disposables); + } +} + +export class UndoIgnoreExtensionRecommendationAction extends Action { + + static readonly ID = 'extensions.ignore'; + + private static readonly Class = 'extension-action undo-ignore'; + + private disposables: IDisposable[] = []; + extension: IExtension; + + constructor( + @IExtensionTipsService private extensionsTipsService: IExtensionTipsService, + ) { + super(UndoIgnoreExtensionRecommendationAction.ID, 'Undo'); + + this.class = UndoIgnoreExtensionRecommendationAction.Class; + this.tooltip = localize('undo', "Undo"); + this.enabled = true; + } + + public run(): TPromise { + this.extensionsTipsService.toggleIgnoredRecommendation(this.extension.id, false); return TPromise.as(null); } diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsList.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsList.ts index 8dcd8b1d2b1..f90c3f99801 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsList.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsList.ts @@ -170,7 +170,7 @@ export class Renderer implements IPagedRenderer { this.updateRecommendationStatus(extension, data); data.extensionDisposables.push(this.extensionTipsService.onRecommendationChange(change => { - if (change.extensionId.toLowerCase() === extension.id.toLowerCase() && change.isRecommended === false) { + if (change.extensionId.toLowerCase() === extension.id.toLowerCase()) { this.updateRecommendationStatus(extension, data); } })); diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/extensionActions.css b/src/vs/workbench/parts/extensions/electron-browser/media/extensionActions.css index b33e9f8095a..43bcde8a007 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/media/extensionActions.css +++ b/src/vs/workbench/parts/extensions/electron-browser/media/extensionActions.css @@ -76,24 +76,3 @@ .extension-editor > .header.recommended > .details > .recommendation > .monaco-action-bar .actions-container { justify-content: flex-start; } - -.extension-editor > .header.recommended > .details > .recommendation > .monaco-action-bar .action-item .action-label.extension-action.ignore { - height: 13px; - width: 8px; - border: none; - outline-offset: 0; - margin-left: 6px; - padding-left: 0; - margin-top: 3px; - background-color: transparent; - color: hsl(0, 66%, 50%); -} - -.hc-black .extension-editor > .header.recommended > .details > .recommendation > .monaco-action-bar .action-item .action-label.extension-action.ignore, -.vs-dark .extension-editor > .header.recommended > .details > .recommendation > .monaco-action-bar .action-item .action-label.extension-action.ignore { - color: hsla(0, 66%, 77%, 1); -} - -.extension-editor > .header.recommended > .details > .recommendation > .monaco-action-bar .action-item .action-label.extension-action.ignore:hover { - filter: brightness(.8); -} \ No newline at end of file diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/extensionEditor.css b/src/vs/workbench/parts/extensions/electron-browser/media/extensionEditor.css index a97d3d273e1..de0b561410a 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/media/extensionEditor.css +++ b/src/vs/workbench/parts/extensions/electron-browser/media/extensionEditor.css @@ -14,8 +14,11 @@ .extension-editor > .header { display: flex; - height: 128px; - padding: 20px; + height: 134px; + padding-top: 20px; + padding-bottom: 14px; + padding-left: 20px; + padding-right: 20px; overflow: hidden; font-size: 14px; } @@ -23,7 +26,7 @@ .extension-editor > .header.recommended, .extension-editor > .header.recommendation-ignored { - height: 140px; + height: 146px; } .extension-editor > .header > .icon { @@ -130,23 +133,47 @@ padding: 1px 6px; } - -.extension-editor > .header.recommended > .details > .recommendation, -.extension-editor > .header.recommendation-ignored > .details > .recommendation { - display: flex; - margin-top: 2px; - font-size: 13px; -} - .extension-editor > .header > .details > .recommendation { display: none; } -.extension-editor > .header.recommendation-ignored > .details > .recommendation > .recommendation-text { +.extension-editor > .header.recommended > .details > .recommendation, +.extension-editor > .header.recommendation-ignored > .details > .recommendation { + display: flex; + margin-top: 0; + font-size: 13px; + height: 25px; font-style: italic; } +.extension-editor > .header.recommended > .details > .recommendation > .monaco-action-bar, .extension-editor > .header.recommendation-ignored > .details > .recommendation > .monaco-action-bar { + margin-left: 4px; + margin-top: 2px; + font-style: normal; +} + +.extension-editor > .header > .details > .recommendation > .recommendation-text { + margin-top: 5px; +} +.extension-editor > .header > .details > .recommendation > .monaco-action-bar .action-label { + margin-top: 4px; + margin-left: 4px; +} + +.extension-editor > .header.recommendation-ignored > .details > .recommendation > .monaco-action-bar .ignore { + display: none; +} + +.extension-editor > .header.recommendation-ignored > .details > .recommendation > .monaco-action-bar .undo-ignore { + display: block; +} + +.extension-editor > .header.recommended > .details > .recommendation > .monaco-action-bar .ignore { + display: block; +} + +.extension-editor > .header.recommended > .details > .recommendation > .monaco-action-bar .undo-ignore { display: none; } diff --git a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsTipsService.test.ts b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsTipsService.test.ts index 1aafa089302..b32f41f8f3c 100644 --- a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsTipsService.test.ts +++ b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsTipsService.test.ts @@ -424,17 +424,16 @@ suite('ExtensionsTipsService Test', () => { return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions, workspaceIgnoredRecommendations).then(() => { testObject = instantiationService.createInstance(ExtensionTipsService); return testObject.loadRecommendationsPromise.then(() => { - const recommendations = testObject.getAllIgnoredRecommendations(); - assert.deepStrictEqual(recommendations, - { - global: ['mockpublisher2.mockextension2'], - workspace: ['ms-vscode.csharp'] - }); + const recommendations = testObject.getAllRecommendationsWithReason(); + assert.ok(recommendations['ms-python.python']); + + assert.ok(!recommendations['mockpublisher2.mockextension2']); + assert.ok(!recommendations['ms-vscode.csharp']); }); }); }); - test('ExtensionTipsService: Able to dynamically ignore global recommendations', () => { + test('ExtensionTipsService: Able to dynamically ignore/unignore global recommendations', () => { const storageGetterStub = (a, _, c) => { const storedRecommendations = '["ms-vscode.csharp", "ms-python.python"]'; const globallyIgnoredRecommendations = '["mockpublisher2.mockextension2"]'; // ignore a workspace recommendation. @@ -452,20 +451,27 @@ suite('ExtensionsTipsService Test', () => { return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions).then(() => { testObject = instantiationService.createInstance(ExtensionTipsService); return testObject.loadRecommendationsPromise.then(() => { - const recommendations = testObject.getAllIgnoredRecommendations(); - assert.deepStrictEqual(recommendations, - { - global: ['mockpublisher2.mockextension2'], - workspace: [] - }); - return testObject.ignoreExtensionRecommendation('mockpublisher1.mockextension1'); + const recommendations = testObject.getAllRecommendationsWithReason(); + assert.ok(recommendations['ms-python.python']); + assert.ok(recommendations['mockpublisher1.mockextension1']); + + assert.ok(!recommendations['mockpublisher2.mockextension2']); + + return testObject.toggleIgnoredRecommendation('mockpublisher1.mockextension1', true); }).then(() => { - const recommendations = testObject.getAllIgnoredRecommendations(); - assert.deepStrictEqual(recommendations, - { - global: ['mockpublisher2.mockextension2', 'mockpublisher1.mockextension1'], - workspace: [] - }); + const recommendations = testObject.getAllRecommendationsWithReason(); + assert.ok(recommendations['ms-python.python']); + + assert.ok(!recommendations['mockpublisher1.mockextension1']); + assert.ok(!recommendations['mockpublisher2.mockextension2']); + + return testObject.toggleIgnoredRecommendation('mockpublisher1.mockextension1', false); + }).then(() => { + const recommendations = testObject.getAllRecommendationsWithReason(); + assert.ok(recommendations['ms-python.python']); + + assert.ok(recommendations['mockpublisher1.mockextension1']); + assert.ok(!recommendations['mockpublisher2.mockextension2']); }); }); }); @@ -484,7 +490,7 @@ suite('ExtensionsTipsService Test', () => { return setUpFolderWorkspace('myFolder', []).then(() => { testObject = instantiationService.createInstance(ExtensionTipsService); testObject.onRecommendationChange(changeHandlerTarget); - testObject.ignoreExtensionRecommendation(ignoredExtensionId); + testObject.toggleIgnoredRecommendation(ignoredExtensionId, true); assert.ok(changeHandlerTarget.calledOnce); assert.ok(changeHandlerTarget.getCall(0).calledWithMatch({ extensionId: 'Some.Extension', isRecommended: false })); From 04cda4357f6e062c1c4ec95217726706d4034590 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 25 Jun 2018 13:32:57 -0700 Subject: [PATCH 053/228] #52810 - Tweak settings editor header padding --- .../parts/preferences/browser/media/settingsEditor2.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css index 031fe1744d5..2e65d0448ab 100644 --- a/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css +++ b/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css @@ -35,7 +35,7 @@ .settings-editor > .settings-header > .settings-advanced-customization { opacity: .7; - margin-top: 10px; + margin-top: 8px; } .settings-editor > .settings-header > .settings-preview-header > .settings-preview-warning { @@ -67,7 +67,7 @@ } .settings-editor > .settings-header > .settings-header-controls { - margin-top: 2px; + margin-top: 3px; height: 30px; display: flex; } From f984189246c612fef209c7c8054ba4c45d2f85da Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 25 Jun 2018 23:06:11 +0200 Subject: [PATCH 054/228] [json] fix for schema assocations in workspace settings --- .../client/src/jsonMain.ts | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/extensions/json-language-features/client/src/jsonMain.ts b/extensions/json-language-features/client/src/jsonMain.ts index 2cd653dfa4b..b2fc5658eb5 100644 --- a/extensions/json-language-features/client/src/jsonMain.ts +++ b/extensions/json-language-features/client/src/jsonMain.ts @@ -251,11 +251,22 @@ function getSettings(): Settings { settings.json!.schemas!.push(schemaSetting); } let fileMatches = setting.fileMatch; + let resultingFileMatches = schemaSetting.fileMatch!; if (Array.isArray(fileMatches)) { if (fileMatchPrefix) { - fileMatches = fileMatches.map(m => fileMatchPrefix + m); + for (let fileMatch of fileMatches) { + if (fileMatch[0] === '/') { + resultingFileMatches.push(fileMatchPrefix + fileMatch); + resultingFileMatches.push(fileMatchPrefix + '/*' + fileMatch); + } else { + resultingFileMatches.push(fileMatchPrefix + '/' + fileMatch); + resultingFileMatches.push(fileMatchPrefix + '/*/' + fileMatch); + } + } + } else { + resultingFileMatches.push(...fileMatches); } - schemaSetting.fileMatch!.push(...fileMatches); + } if (setting.schema) { schemaSetting.schema = setting.schema; @@ -276,10 +287,10 @@ function getSettings(): Settings { let folderSchemas = schemaConfigInfo!.workspaceFolderValue; if (Array.isArray(folderSchemas)) { let folderPath = folderUri.toString(); - if (folderPath[folderPath.length - 1] !== '/') { - folderPath = folderPath + '/'; + if (folderPath[folderPath.length - 1] === '/') { + folderPath = folderPath.substr(0, folderPath.length - 1); } - collectSchemaSettings(folderSchemas, folderUri.fsPath, folderPath + '*'); + collectSchemaSettings(folderSchemas, folderUri.fsPath, folderPath); } } } From 7f56f200c1a465a21861f1b1e4b577a78ca0369e Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 25 Jun 2018 23:30:06 +0200 Subject: [PATCH 055/228] Separate grammar for jsonc (fixes #42539) --- extensions/json/build/update-grammars.js | 41 ++++ extensions/json/package.json | 6 +- .../json/syntaxes/JSONC.tmLanguage.json | 213 ++++++++++++++++++ .../theme-defaults/themes/light_vs.json | 5 +- .../test/colorize-results/tsconfig_json.json | 40 ++-- 5 files changed, 281 insertions(+), 24 deletions(-) create mode 100644 extensions/json/build/update-grammars.js create mode 100644 extensions/json/syntaxes/JSONC.tmLanguage.json diff --git a/extensions/json/build/update-grammars.js b/extensions/json/build/update-grammars.js new file mode 100644 index 00000000000..191ef935444 --- /dev/null +++ b/extensions/json/build/update-grammars.js @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +var updateGrammar = require('../../../build/npm/update-grammar'); + +function adaptJSON(grammar, replacementScope) { + grammar.name = 'JSON with comments'; + grammar.scopeName = `source${replacementScope}`; + + var fixScopeNames = function(rule) { + if (typeof rule.name === 'string') { + rule.name = rule.name.replace(/\.json/g, replacementScope); + } + if (typeof rule.contentName === 'string') { + rule.contentName = rule.contentName.replace(/\.json/g, replacementScope); + } + for (var property in rule) { + var value = rule[property]; + if (typeof value === 'object') { + fixScopeNames(value); + } + } + }; + + var repository = grammar.repository; + for (var key in repository) { + fixScopeNames(repository[key]); + } +} + +var tsGrammarRepo = 'Microsoft/vscode-JSON.tmLanguage'; +updateGrammar.update(tsGrammarRepo, 'JSON.tmLanguage', './syntaxes/JSON.tmLanguage.json'); +updateGrammar.update(tsGrammarRepo, 'JSON.tmLanguage', './syntaxes/JSONC.tmLanguage.json', grammar => adaptJSON(grammar, '.jsonc')); + + + + + diff --git a/extensions/json/package.json b/extensions/json/package.json index 9ef7b078963..12b3ccf9ef6 100644 --- a/extensions/json/package.json +++ b/extensions/json/package.json @@ -8,7 +8,7 @@ "vscode": "0.10.x" }, "scripts": { - "update-grammar": "node ../../build/npm/update-grammar.js Microsoft/vscode-JSON.tmLanguage JSON.tmLanguage ./syntaxes/JSON.tmLanguage.json" + "update-grammar": "node ./build/update-grammars.js" }, "contributes": { "languages": [ @@ -58,8 +58,8 @@ }, { "language": "jsonc", - "scopeName": "source.json", - "path": "./syntaxes/JSON.tmLanguage.json" + "scopeName": "source.jsonc", + "path": "./syntaxes/JSONC.tmLanguage.json" } ], "jsonValidation": [ diff --git a/extensions/json/syntaxes/JSONC.tmLanguage.json b/extensions/json/syntaxes/JSONC.tmLanguage.json new file mode 100644 index 00000000000..cea3c59d5aa --- /dev/null +++ b/extensions/json/syntaxes/JSONC.tmLanguage.json @@ -0,0 +1,213 @@ +{ + "information_for_contributors": [ + "This file has been converted from https://github.com/Microsoft/vscode-JSON.tmLanguage/blob/master/JSON.tmLanguage", + "If you want to provide a fix or improvement, please create a pull request against the original repository.", + "Once accepted there, we are happy to receive an update request." + ], + "version": "https://github.com/Microsoft/vscode-JSON.tmLanguage/commit/9bd83f1c252b375e957203f21793316203f61f70", + "name": "JSON with comments", + "scopeName": "source.jsonc", + "patterns": [ + { + "include": "#value" + } + ], + "repository": { + "array": { + "begin": "\\[", + "beginCaptures": { + "0": { + "name": "punctuation.definition.array.begin.jsonc" + } + }, + "end": "\\]", + "endCaptures": { + "0": { + "name": "punctuation.definition.array.end.jsonc" + } + }, + "name": "meta.structure.array.jsonc", + "patterns": [ + { + "include": "#value" + }, + { + "match": ",", + "name": "punctuation.separator.array.jsonc" + }, + { + "match": "[^\\s\\]]", + "name": "invalid.illegal.expected-array-separator.jsonc" + } + ] + }, + "comments": { + "patterns": [ + { + "begin": "/\\*\\*(?!/)", + "captures": { + "0": { + "name": "punctuation.definition.comment.jsonc" + } + }, + "end": "\\*/", + "name": "comment.block.documentation.jsonc" + }, + { + "begin": "/\\*", + "captures": { + "0": { + "name": "punctuation.definition.comment.jsonc" + } + }, + "end": "\\*/", + "name": "comment.block.jsonc" + }, + { + "captures": { + "1": { + "name": "punctuation.definition.comment.jsonc" + } + }, + "match": "(//).*$\\n?", + "name": "comment.line.double-slash.js" + } + ] + }, + "constant": { + "match": "\\b(?:true|false|null)\\b", + "name": "constant.language.jsonc" + }, + "number": { + "match": "(?x) # turn on extended mode\n -? # an optional minus\n (?:\n 0 # a zero\n | # ...or...\n [1-9] # a 1-9 character\n \\d* # followed by zero or more digits\n )\n (?:\n (?:\n \\. # a period\n \\d+ # followed by one or more digits\n )?\n (?:\n [eE] # an e character\n [+-]? # followed by an option +/-\n \\d+ # followed by one or more digits\n )? # make exponent optional\n )? # make decimal portion optional", + "name": "constant.numeric.jsonc" + }, + "object": { + "begin": "\\{", + "beginCaptures": { + "0": { + "name": "punctuation.definition.dictionary.begin.jsonc" + } + }, + "end": "\\}", + "endCaptures": { + "0": { + "name": "punctuation.definition.dictionary.end.jsonc" + } + }, + "name": "meta.structure.dictionary.jsonc", + "patterns": [ + { + "comment": "the JSON object key", + "include": "#objectkey" + }, + { + "include": "#comments" + }, + { + "begin": ":", + "beginCaptures": { + "0": { + "name": "punctuation.separator.dictionary.key-value.jsonc" + } + }, + "end": "(,)|(?=\\})", + "endCaptures": { + "1": { + "name": "punctuation.separator.dictionary.pair.jsonc" + } + }, + "name": "meta.structure.dictionary.value.jsonc", + "patterns": [ + { + "comment": "the JSON object value", + "include": "#value" + }, + { + "match": "[^\\s,]", + "name": "invalid.illegal.expected-dictionary-separator.jsonc" + } + ] + }, + { + "match": "[^\\s\\}]", + "name": "invalid.illegal.expected-dictionary-separator.jsonc" + } + ] + }, + "string": { + "begin": "\"", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.jsonc" + } + }, + "end": "\"", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.jsonc" + } + }, + "name": "string.quoted.double.jsonc", + "patterns": [ + { + "include": "#stringcontent" + } + ] + }, + "objectkey": { + "begin": "\"", + "beginCaptures": { + "0": { + "name": "punctuation.support.type.property-name.begin.jsonc" + } + }, + "end": "\"", + "endCaptures": { + "0": { + "name": "punctuation.support.type.property-name.end.jsonc" + } + }, + "name": "string.jsonc support.type.property-name.jsonc", + "patterns": [ + { + "include": "#stringcontent" + } + ] + }, + "stringcontent": { + "patterns": [ + { + "match": "(?x) # turn on extended mode\n \\\\ # a literal backslash\n (?: # ...followed by...\n [\"\\\\/bfnrt] # one of these characters\n | # ...or...\n u # a u\n [0-9a-fA-F]{4}) # and four hex digits", + "name": "constant.character.escape.jsonc" + }, + { + "match": "\\\\.", + "name": "invalid.illegal.unrecognized-string-escape.jsonc" + } + ] + }, + "value": { + "patterns": [ + { + "include": "#constant" + }, + { + "include": "#number" + }, + { + "include": "#string" + }, + { + "include": "#array" + }, + { + "include": "#object" + }, + { + "include": "#comments" + } + ] + } + } +} \ No newline at end of file diff --git a/extensions/theme-defaults/themes/light_vs.json b/extensions/theme-defaults/themes/light_vs.json index 0fafec36645..a73b42a6c89 100644 --- a/extensions/theme-defaults/themes/light_vs.json +++ b/extensions/theme-defaults/themes/light_vs.json @@ -290,7 +290,10 @@ } }, { - "scope": "support.type.property-name.json", + "scope": [ + "support.type.property-name.json", + "support.type.property-name.jsonc", + ], "settings": { "foreground": "#0451a5" } diff --git a/extensions/typescript-basics/test/colorize-results/tsconfig_json.json b/extensions/typescript-basics/test/colorize-results/tsconfig_json.json index 13700965aba..e9dc59f3db4 100644 --- a/extensions/typescript-basics/test/colorize-results/tsconfig_json.json +++ b/extensions/typescript-basics/test/colorize-results/tsconfig_json.json @@ -1,7 +1,7 @@ [ { "c": "{", - "t": "source.json meta.structure.dictionary.json punctuation.definition.dictionary.begin.json", + "t": "source.json.jsonc meta.structure.dictionary.json.jsonc punctuation.definition.dictionary.begin.json.jsonc", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -12,7 +12,7 @@ }, { "c": "\t", - "t": "source.json meta.structure.dictionary.json", + "t": "source.json.jsonc meta.structure.dictionary.json.jsonc", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -23,7 +23,7 @@ }, { "c": "\"", - "t": "source.json meta.structure.dictionary.json string.json support.type.property-name.json punctuation.support.type.property-name.begin.json", + "t": "source.json.jsonc meta.structure.dictionary.json.jsonc string.json.jsonc support.type.property-name.json.jsonc punctuation.support.type.property-name.begin.json.jsonc", "r": { "dark_plus": "support.type.property-name: #9CDCFE", "light_plus": "support.type.property-name.json: #0451A5", @@ -34,7 +34,7 @@ }, { "c": "compilerOptions", - "t": "source.json meta.structure.dictionary.json string.json support.type.property-name.json", + "t": "source.json.jsonc meta.structure.dictionary.json.jsonc string.json.jsonc support.type.property-name.json.jsonc", "r": { "dark_plus": "support.type.property-name: #9CDCFE", "light_plus": "support.type.property-name.json: #0451A5", @@ -45,7 +45,7 @@ }, { "c": "\"", - "t": "source.json meta.structure.dictionary.json string.json support.type.property-name.json punctuation.support.type.property-name.end.json", + "t": "source.json.jsonc meta.structure.dictionary.json.jsonc string.json.jsonc support.type.property-name.json.jsonc punctuation.support.type.property-name.end.json.jsonc", "r": { "dark_plus": "support.type.property-name: #9CDCFE", "light_plus": "support.type.property-name.json: #0451A5", @@ -56,7 +56,7 @@ }, { "c": ":", - "t": "source.json meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.separator.dictionary.key-value.json", + "t": "source.json.jsonc meta.structure.dictionary.json.jsonc meta.structure.dictionary.value.json.jsonc punctuation.separator.dictionary.key-value.json.jsonc", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -67,7 +67,7 @@ }, { "c": " ", - "t": "source.json meta.structure.dictionary.json meta.structure.dictionary.value.json", + "t": "source.json.jsonc meta.structure.dictionary.json.jsonc meta.structure.dictionary.value.json.jsonc", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -78,7 +78,7 @@ }, { "c": "{", - "t": "source.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json punctuation.definition.dictionary.begin.json", + "t": "source.json.jsonc meta.structure.dictionary.json.jsonc meta.structure.dictionary.value.json.jsonc meta.structure.dictionary.json.jsonc punctuation.definition.dictionary.begin.json.jsonc", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -89,7 +89,7 @@ }, { "c": "\t\t", - "t": "source.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json", + "t": "source.json.jsonc meta.structure.dictionary.json.jsonc meta.structure.dictionary.value.json.jsonc meta.structure.dictionary.json.jsonc", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -100,7 +100,7 @@ }, { "c": "\"", - "t": "source.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json string.json support.type.property-name.json punctuation.support.type.property-name.begin.json", + "t": "source.json.jsonc meta.structure.dictionary.json.jsonc meta.structure.dictionary.value.json.jsonc meta.structure.dictionary.json.jsonc string.json.jsonc support.type.property-name.json.jsonc punctuation.support.type.property-name.begin.json.jsonc", "r": { "dark_plus": "support.type.property-name: #9CDCFE", "light_plus": "support.type.property-name.json: #0451A5", @@ -111,7 +111,7 @@ }, { "c": "target", - "t": "source.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json string.json support.type.property-name.json", + "t": "source.json.jsonc meta.structure.dictionary.json.jsonc meta.structure.dictionary.value.json.jsonc meta.structure.dictionary.json.jsonc string.json.jsonc support.type.property-name.json.jsonc", "r": { "dark_plus": "support.type.property-name: #9CDCFE", "light_plus": "support.type.property-name.json: #0451A5", @@ -122,7 +122,7 @@ }, { "c": "\"", - "t": "source.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json string.json support.type.property-name.json punctuation.support.type.property-name.end.json", + "t": "source.json.jsonc meta.structure.dictionary.json.jsonc meta.structure.dictionary.value.json.jsonc meta.structure.dictionary.json.jsonc string.json.jsonc support.type.property-name.json.jsonc punctuation.support.type.property-name.end.json.jsonc", "r": { "dark_plus": "support.type.property-name: #9CDCFE", "light_plus": "support.type.property-name.json: #0451A5", @@ -133,7 +133,7 @@ }, { "c": ":", - "t": "source.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.separator.dictionary.key-value.json", + "t": "source.json.jsonc meta.structure.dictionary.json.jsonc meta.structure.dictionary.value.json.jsonc meta.structure.dictionary.json.jsonc meta.structure.dictionary.value.json.jsonc punctuation.separator.dictionary.key-value.json.jsonc", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -144,7 +144,7 @@ }, { "c": " ", - "t": "source.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json", + "t": "source.json.jsonc meta.structure.dictionary.json.jsonc meta.structure.dictionary.value.json.jsonc meta.structure.dictionary.json.jsonc meta.structure.dictionary.value.json.jsonc", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -155,7 +155,7 @@ }, { "c": "\"", - "t": "source.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json punctuation.definition.string.begin.json", + "t": "source.json.jsonc meta.structure.dictionary.json.jsonc meta.structure.dictionary.value.json.jsonc meta.structure.dictionary.json.jsonc meta.structure.dictionary.value.json.jsonc string.quoted.double.json.jsonc punctuation.definition.string.begin.json.jsonc", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -166,7 +166,7 @@ }, { "c": "es6", - "t": "source.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json", + "t": "source.json.jsonc meta.structure.dictionary.json.jsonc meta.structure.dictionary.value.json.jsonc meta.structure.dictionary.json.jsonc meta.structure.dictionary.value.json.jsonc string.quoted.double.json.jsonc", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -177,7 +177,7 @@ }, { "c": "\"", - "t": "source.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json punctuation.definition.string.end.json", + "t": "source.json.jsonc meta.structure.dictionary.json.jsonc meta.structure.dictionary.value.json.jsonc meta.structure.dictionary.json.jsonc meta.structure.dictionary.value.json.jsonc string.quoted.double.json.jsonc punctuation.definition.string.end.json.jsonc", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -188,7 +188,7 @@ }, { "c": "\t", - "t": "source.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json", + "t": "source.json.jsonc meta.structure.dictionary.json.jsonc meta.structure.dictionary.value.json.jsonc meta.structure.dictionary.json.jsonc meta.structure.dictionary.value.json.jsonc", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -199,7 +199,7 @@ }, { "c": "}", - "t": "source.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json punctuation.definition.dictionary.end.json", + "t": "source.json.jsonc meta.structure.dictionary.json.jsonc meta.structure.dictionary.value.json.jsonc meta.structure.dictionary.json.jsonc punctuation.definition.dictionary.end.json.jsonc", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -210,7 +210,7 @@ }, { "c": "}", - "t": "source.json meta.structure.dictionary.json punctuation.definition.dictionary.end.json", + "t": "source.json.jsonc meta.structure.dictionary.json.jsonc punctuation.definition.dictionary.end.json.jsonc", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", From f05515510898c62049a438bf448960a163800ea9 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 25 Jun 2018 23:32:50 +0200 Subject: [PATCH 056/228] update service --- .../json-language-features/server/package.json | 2 +- .../json-language-features/server/yarn.lock | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/extensions/json-language-features/server/package.json b/extensions/json-language-features/server/package.json index 8ae80565173..532345f51ee 100644 --- a/extensions/json-language-features/server/package.json +++ b/extensions/json-language-features/server/package.json @@ -13,7 +13,7 @@ "dependencies": { "jsonc-parser": "^2.0.0-next.1", "request-light": "^0.2.2", - "vscode-json-languageservice": "^3.1.2-next.3", + "vscode-json-languageservice": "^3.1.2", "vscode-languageserver": "^4.1.3", "vscode-languageserver-protocol-foldingprovider": "^2.0.1", "vscode-nls": "^3.2.2", diff --git a/extensions/json-language-features/server/yarn.lock b/extensions/json-language-features/server/yarn.lock index ce5677e6ae0..babef1f7177 100644 --- a/extensions/json-language-features/server/yarn.lock +++ b/extensions/json-language-features/server/yarn.lock @@ -52,14 +52,14 @@ https-proxy-agent@2.1.1: agent-base "^4.1.0" debug "^3.1.0" -jsonc-parser@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.0.0.tgz#62ff087a7e753875febf3c55f1fc0cd737c36b5a" - jsonc-parser@^2.0.0-next.1: version "2.0.0-next.1" resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.0.0-next.1.tgz#445a824f765a96abfbb286d759a9b1d226b18088" +jsonc-parser@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.0.1.tgz#9d23cd2709714fff508a1a6679d82135bee1ae60" + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -72,11 +72,11 @@ request-light@^0.2.2: https-proxy-agent "2.1.1" vscode-nls "^2.0.2" -vscode-json-languageservice@^3.1.2-next.3: - version "3.1.2-next.3" - resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-3.1.2-next.3.tgz#cc0902148f898b413987fb1b4c4a9e7fc1a79c78" +vscode-json-languageservice@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-3.1.2.tgz#5c70fc32ad389e6da48452e7b0187ea5e70f68bf" dependencies: - jsonc-parser "^2.0.0" + jsonc-parser "^2.0.1" vscode-languageserver-types "^3.7.2" vscode-nls "^3.2.2" vscode-uri "^1.0.3" From 66a8535e19835f500945e0f5f79f991ec6c93046 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 25 Jun 2018 13:43:04 -0700 Subject: [PATCH 057/228] Settings editor - shorter bool rows --- .../parts/preferences/browser/settingsTree.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/parts/preferences/browser/settingsTree.ts b/src/vs/workbench/parts/preferences/browser/settingsTree.ts index c2492110f96..2d84b3ac8c2 100644 --- a/src/vs/workbench/parts/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/parts/preferences/browser/settingsTree.ts @@ -439,6 +439,7 @@ export interface ISettingChangeEvent { export class SettingsRenderer implements IRenderer { private static readonly SETTING_ROW_HEIGHT = 94; + private static readonly SETTING_BOOL_ROW_HEIGHT = 61; private readonly _onDidChangeSetting: Emitter = new Emitter(); public readonly onDidChangeSetting: Event = this._onDidChangeSetting.event; @@ -466,13 +467,21 @@ export class SettingsRenderer implements IRenderer { if (isSelected) { return this.measureSettingElementHeight(tree, element); } else { - return SettingsRenderer.SETTING_ROW_HEIGHT; + return this._getUnexpandedSettingHeight(element); } } return 0; } + _getUnexpandedSettingHeight(element: SettingsTreeSettingElement): number { + if (element.valueType === 'boolean') { + return SettingsRenderer.SETTING_BOOL_ROW_HEIGHT; + } else { + return SettingsRenderer.SETTING_ROW_HEIGHT; + } + } + private measureSettingElementHeight(tree: ITree, element: SettingsTreeSettingElement): number { const measureHelper = DOM.append(this.measureContainer, $('.setting-measure-helper')); @@ -482,7 +491,7 @@ export class SettingsRenderer implements IRenderer { const height = this.measureContainer.offsetHeight; this.measureContainer.removeChild(this.measureContainer.firstChild); - return Math.max(height, SettingsRenderer.SETTING_ROW_HEIGHT); + return Math.max(height, this._getUnexpandedSettingHeight(element)); } getTemplateId(tree: ITree, element: SettingsTreeElement): string { From 2ece4b2ca40bed211f6395b9ecc4dda2989810df Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 25 Jun 2018 14:00:54 -0700 Subject: [PATCH 058/228] Settings editor - add "workbench/settings" group --- src/vs/workbench/parts/preferences/browser/settingsLayout.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vs/workbench/parts/preferences/browser/settingsLayout.ts b/src/vs/workbench/parts/preferences/browser/settingsLayout.ts index 65a5ec6eda2..9acee728414 100644 --- a/src/vs/workbench/parts/preferences/browser/settingsLayout.ts +++ b/src/vs/workbench/parts/preferences/browser/settingsLayout.ts @@ -89,6 +89,11 @@ export const tocData: ITOCEntry = { label: localize('editorManagement', "Editor Management"), settings: ['workbench.editor.*'] }, + { + id: 'workbench/settings', + label: localize('settings', "Settings Editor"), + settings: ['workbench.settings.*'] + }, { id: 'workbench/zenmode', label: localize('zenMode', "Zen Mode"), From 0b0793624437ba734421395f96aafe6d38cfee01 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 25 Jun 2018 14:44:24 -0700 Subject: [PATCH 059/228] Settings editor - fix refreshing when settings target changes. Add right-padding to setting --- .../preferences/browser/media/settingsEditor2.css | 1 + .../parts/preferences/browser/settingsEditor2.ts | 4 +++- .../parts/preferences/browser/settingsTree.ts | 14 ++++++++------ 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css index 2e65d0448ab..f6567390f89 100644 --- a/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css +++ b/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css @@ -258,6 +258,7 @@ .settings-editor > .settings-body > .settings-tree-container .group-title, .settings-editor > .settings-body > .settings-tree-container .setting-item { padding-left: 10px; + padding-right: 10px; } .settings-editor > .settings-body > .settings-tree-container .group-title { diff --git a/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts b/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts index 7d26a796a44..3b886e64f7a 100644 --- a/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts @@ -191,7 +191,9 @@ export class SettingsEditor2 extends BaseEditor { this.settingsTargetsWidget.settingsTarget = ConfigurationTarget.USER; this.settingsTargetsWidget.onDidTargetChange(() => { this.viewState.settingsTarget = this.settingsTargetsWidget.settingsTarget; - this.settingsTree.refresh(); + + this.settingsTreeModel.update(); + this.refreshTreeAndMaintainFocus(); }); this.createHeaderControls(headerControlsContainer); diff --git a/src/vs/workbench/parts/preferences/browser/settingsTree.ts b/src/vs/workbench/parts/preferences/browser/settingsTree.ts index 2d84b3ac8c2..9ed6666a967 100644 --- a/src/vs/workbench/parts/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/parts/preferences/browser/settingsTree.ts @@ -47,11 +47,13 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { }); registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { + // TODO@roblou Hacks! Make checkbox background themeable const selectBackgroundColor = theme.getColor(selectBackground); if (selectBackgroundColor) { collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item-bool .setting-value-checkbox { background-color: ${selectBackgroundColor} !important; }`); } + // TODO@roblou Hacks! Use proper inputbox theming instead of !important const selectBorderColor = theme.getColor(selectBorder); if (selectBorderColor) { collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item-bool .setting-value-checkbox { border-color: ${selectBorderColor} !important; }`); @@ -96,18 +98,18 @@ export class SettingsTreeModel { private _treeElementsById = new Map(); constructor( - private viewState: ISettingsEditorViewState, - tocRoot: ITOCEntry, - @IConfigurationService private configurationService: IConfigurationService + private _viewState: ISettingsEditorViewState, + private _tocRoot: ITOCEntry, + @IConfigurationService private _configurationService: IConfigurationService ) { - this.update(tocRoot); + this.update(this._tocRoot); } get root(): SettingsTreeElement { return this._root; } - update(newTocRoot: ITOCEntry): void { + update(newTocRoot = this._tocRoot): void { const newRoot = this.createSettingsTreeGroupElement(newTocRoot); if (this._root) { this._root.children = newRoot.children; @@ -146,7 +148,7 @@ export class SettingsTreeModel { } private createSettingsTreeSettingElement(setting: ISetting, parent: SettingsTreeGroupElement): SettingsTreeSettingElement { - const element = createSettingsTreeSettingElement(setting, parent, this.viewState.settingsTarget, this.configurationService); + const element = createSettingsTreeSettingElement(setting, parent, this._viewState.settingsTarget, this._configurationService); this._treeElementsById.set(element.id, element); return element; } From 16625563408524e451171eb27e2d7c69de209c98 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Mon, 25 Jun 2018 15:21:49 -0700 Subject: [PATCH 060/228] Fix race bug (#52843) * Fix race bug * replace the fix with a fix that actualy fixes * Remove unused import --- .../electron-browser/extensionTipsService.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts index 2a77811139a..d64b5397088 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts @@ -213,7 +213,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe Object.keys(this._sessionRestoredRecommendations).forEach(x => output[x.toLowerCase()] = { reasonId: this._sessionRestoredRecommendations[x].reasonId, - reasonText: localize('restoredRecommendation', "You will receive recommendations for this extension in your next VS Code session.") + reasonText: localize('restoredRecommendation', "You will receive recommendations for this extension in your future VS Code sessions.") }); return output; @@ -226,7 +226,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe private fetchWorkspaceRecommendations(): TPromise { this._workspaceIgnoredRecommendations = []; - this._allWorkspaceRecommendedExtensions = []; + const tmpAllWorkspaceRecommendations = []; if (!this.isEnabled) { return TPromise.as(null); } @@ -255,10 +255,10 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe for (const r of contentsBySource.contents.recommendations) { const extensionId = r.toLowerCase(); if (invalidExtensions.indexOf(extensionId) === -1) { - let recommendation = this._allWorkspaceRecommendedExtensions.filter(r => r.extensionId === extensionId)[0]; + let recommendation = tmpAllWorkspaceRecommendations.filter(r => r.extensionId === extensionId)[0]; if (!recommendation) { recommendation = { extensionId, sources: [] }; - this._allWorkspaceRecommendedExtensions.push(recommendation); + tmpAllWorkspaceRecommendations.push(recommendation); } if (recommendation.sources.indexOf(contentsBySource.source) === -1) { recommendation.sources.push(contentsBySource.source); @@ -267,7 +267,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe } } } - + this._allWorkspaceRecommendedExtensions = tmpAllWorkspaceRecommendations; this._allIgnoredRecommendations = distinct([...this._globallyIgnoredRecommendations, ...this._workspaceIgnoredRecommendations]); this.refilterAllRecommendations(); })); From e663710ffa019565a6ac842ce614a79a0637c210 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Mon, 25 Jun 2018 15:59:01 -0700 Subject: [PATCH 061/228] fixes #52284 --- src/vs/platform/windows/common/windows.ts | 1 + src/vs/platform/windows/common/windowsIpc.ts | 6 + .../windows/electron-main/windowsService.ts | 25 +++- .../parts/menubar/menubar.contribution.ts | 14 +-- src/vs/workbench/electron-browser/actions.ts | 119 +++++++++++++++++- .../electron-browser/main.contribution.ts | 13 +- .../workbench/test/workbenchTestServices.ts | 4 + 7 files changed, 171 insertions(+), 11 deletions(-) diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index 08102ecfce2..8df44ae6f68 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -169,6 +169,7 @@ export interface IWindowsService { // TODO: this is a bit backwards startCrashReporter(config: CrashReporterStartOptions): TPromise; + openAccessibilityOptions(): TPromise; openAboutDialog(): TPromise; } diff --git a/src/vs/platform/windows/common/windowsIpc.ts b/src/vs/platform/windows/common/windowsIpc.ts index 7fdd5c8bbd8..3cc5e90f688 100644 --- a/src/vs/platform/windows/common/windowsIpc.ts +++ b/src/vs/platform/windows/common/windowsIpc.ts @@ -65,6 +65,7 @@ export interface IWindowsChannel extends IChannel { call(command: 'showItemInFolder', arg: string): TPromise; call(command: 'openExternal', arg: string): TPromise; call(command: 'startCrashReporter', arg: CrashReporterStartOptions): TPromise; + call(command: 'openAccessibilityOptions'): TPromise; call(command: 'openAboutDialog'): TPromise; call(command: string, arg?: any): TPromise; } @@ -152,6 +153,7 @@ export class WindowsChannel implements IWindowsChannel { case 'showItemInFolder': return this.service.showItemInFolder(arg); case 'openExternal': return this.service.openExternal(arg); case 'startCrashReporter': return this.service.startCrashReporter(arg); + case 'openAccessibilityOptions': return this.service.openAccessibilityOptions(); case 'openAboutDialog': return this.service.openAboutDialog(); } return undefined; @@ -367,6 +369,10 @@ export class WindowsChannelClient implements IWindowsService { return this.channel.call('updateTouchBar', [windowId, items]); } + openAccessibilityOptions(): TPromise { + return this.channel.call('openAccessibilityOptions'); + } + openAboutDialog(): TPromise { return this.channel.call('openAboutDialog'); } diff --git a/src/vs/platform/windows/electron-main/windowsService.ts b/src/vs/platform/windows/electron-main/windowsService.ts index 12d8e421d78..e9a6e08c684 100644 --- a/src/vs/platform/windows/electron-main/windowsService.ts +++ b/src/vs/platform/windows/electron-main/windowsService.ts @@ -13,7 +13,7 @@ import URI from 'vs/base/common/uri'; import product from 'vs/platform/node/product'; import { IWindowsService, OpenContext, INativeOpenDialogOptions, IEnterWorkspaceResult, IMessageBoxResult, IDevToolsOptions } from 'vs/platform/windows/common/windows'; import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; -import { shell, crashReporter, app, Menu, clipboard } from 'electron'; +import { shell, crashReporter, app, Menu, clipboard, BrowserWindow } from 'electron'; import { Event, fromNodeEventEmitter, mapEvent, filterEvent, anyEvent } from 'vs/base/common/event'; import { IURLService, IURLHandler } from 'vs/platform/url/common/url'; import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; @@ -468,6 +468,29 @@ export class WindowsService implements IWindowsService, IURLHandler, IDisposable return TPromise.as(null); } + openAccessibilityOptions(): TPromise { + this.logService.trace('windowsService#openAccessibilityOptions'); + + const win = new BrowserWindow({ + alwaysOnTop: true, + skipTaskbar: true, + resizable: false, + width: 450, + height: 300, + show: true, + title: nls.localize('accessibilityOptionsWindowTitle', "Accessibility Options"), + webPreferences: { + disableBlinkFeatures: 'Auxclick' + } + }); + + win.setMenuBarVisibility(false); + + win.loadURL('chrome://accessibility'); + + return TPromise.as(null); + } + openAboutDialog(): TPromise { this.logService.trace('windowsService#openAboutDialog'); const lastActiveWindow = this.windowsMainService.getFocusedWindow() || this.windowsMainService.getLastActiveWindow(); diff --git a/src/vs/workbench/browser/parts/menubar/menubar.contribution.ts b/src/vs/workbench/browser/parts/menubar/menubar.contribution.ts index a156d5fcc77..efa899b823c 100644 --- a/src/vs/workbench/browser/parts/menubar/menubar.contribution.ts +++ b/src/vs/workbench/browser/parts/menubar/menubar.contribution.ts @@ -1378,7 +1378,7 @@ function helpMenuRegistration() { MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { group: '1_welcome', command: { - id: 'workbench.action.showCurrentReleaseNotes', + id: 'update.showCurrentReleaseNotes', title: nls.localize({ key: 'miReleaseNotes', comment: ['&& denotes a mnemonic'] }, "&&Release Notes") }, order: 4 @@ -1416,7 +1416,7 @@ function helpMenuRegistration() { MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { group: '3_feedback', command: { - id: 'openTwitterUrl', + id: 'workbench.action.openTwitterUrl', title: nls.localize({ key: 'miTwitter', comment: ['&& denotes a mnemonic'] }, "&&Join us on Twitter") }, order: 1 @@ -1425,7 +1425,7 @@ function helpMenuRegistration() { MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { group: '3_feedback', command: { - id: 'openUserVoiceUrl', + id: 'workbench.action.openRequestFeatureUrl', title: nls.localize({ key: 'miUserVoice', comment: ['&& denotes a mnemonic'] }, "&&Search Feature Requests") }, order: 2 @@ -1434,7 +1434,7 @@ function helpMenuRegistration() { MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { group: '3_feedback', command: { - id: 'openReportIssues', + id: 'workbench.action.openIssueReporter', title: nls.localize({ key: 'miReportIssue', comment: ['&& denotes a mnemonic', 'Translate this to "Report Issue in English" in all languages please!'] }, "Report &&Issue") }, order: 3 @@ -1444,7 +1444,7 @@ function helpMenuRegistration() { MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { group: '4_legal', command: { - id: 'openLicenseUrl', + id: 'workbench.action.openLicenseUrl', title: nls.localize({ key: 'miLicense', comment: ['&& denotes a mnemonic'] }, "View &&License") }, order: 1 @@ -1453,7 +1453,7 @@ function helpMenuRegistration() { MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { group: '4_legal', command: { - id: 'openPrivacyStatement', + id: 'workbench.action.openPrivacyStatementUrl', title: nls.localize({ key: 'miPrivacyStatement', comment: ['&& denotes a mnemonic'] }, "&&Privacy Statement") }, order: 2 @@ -1481,7 +1481,7 @@ function helpMenuRegistration() { MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { group: '5_tools', command: { - id: 'accessibilityOptions', + id: 'workbench.action.showAccessibilityOptions', title: nls.localize({ key: 'miAccessibilityOptions', comment: ['&& denotes a mnemonic'] }, "Accessibility &&Options") }, order: 3 diff --git a/src/vs/workbench/electron-browser/actions.ts b/src/vs/workbench/electron-browser/actions.ts index 028e7c374cc..00e815d1b81 100644 --- a/src/vs/workbench/electron-browser/actions.ts +++ b/src/vs/workbench/electron-browser/actions.ts @@ -20,7 +20,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import * as paths from 'vs/base/common/paths'; -import { isMacintosh, isLinux } from 'vs/base/common/platform'; +import { isMacintosh, isLinux, language } from 'vs/base/common/platform'; import { IQuickOpenService, IFilePickOpenEntry, ISeparator, IPickOpenAction, IPickOpenItem } from 'vs/platform/quickOpen/common/quickOpen'; import * as browser from 'vs/base/browser/browser'; import { IIntegrityService } from 'vs/platform/integrity/common/integrity'; @@ -32,7 +32,7 @@ import { IPartService, Parts, Position as PartPosition } from 'vs/workbench/serv import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import * as os from 'os'; -import { webFrame } from 'electron'; +import { webFrame, shell } from 'electron'; import { getPathLabel, getBaseLabel } from 'vs/base/common/labels'; import { IViewlet } from 'vs/workbench/common/viewlet'; import { IPanel } from 'vs/workbench/common/panel'; @@ -1542,6 +1542,121 @@ export class ToggleWindowTabsBar extends Action { } } +export class OpenTwitterUrlAction extends Action { + + public static readonly ID = 'workbench.action.openTwitterUrl'; + public static LABEL = nls.localize('openTwitterUrl', "Join us on Twitter", product.applicationName); + + constructor( + id: string, + label: string + ) { + super(id, label); + } + + run(): TPromise { + if (product.twitterUrl) { + return TPromise.as(shell.openExternal(product.twitterUrl)); + } + + return TPromise.as(false); + } +} + +export class OpenRequestFeatureUrlAction extends Action { + + public static readonly ID = 'workbench.action.openRequestFeatureUrl'; + public static LABEL = nls.localize('openUserVoiceUrl', "Search Feature Requests"); + + constructor( + id: string, + label: string + ) { + super(id, label); + } + + run(): TPromise { + if (product.requestFeatureUrl) { + return TPromise.as(shell.openExternal(product.requestFeatureUrl)); + } + + return TPromise.as(false); + } +} + +export class OpenLicenseUrlAction extends Action { + + public static readonly ID = 'workbench.action.openLicenseUrl'; + public static LABEL = nls.localize('openLicenseUrl', "View License"); + + constructor( + id: string, + label: string + ) { + super(id, label); + } + + run(): TPromise { + if (product.licenseUrl) { + if (language) { + const queryArgChar = product.licenseUrl.indexOf('?') > 0 ? '&' : '?'; + return TPromise.as(shell.openExternal(`${product.licenseUrl}${queryArgChar}lang=${language}`)); + } else { + return TPromise.as(shell.openExternal(product.licenseUrl)); + } + } + + return TPromise.as(false); + } +} + + +export class OpenPrivacyStatementUrlAction extends Action { + + public static readonly ID = 'workbench.action.openPrivacyStatementUrl'; + public static LABEL = nls.localize('openPrivacyStatement', "Privacy Statement"); + + constructor( + id: string, + label: string + ) { + super(id, label); + } + + run(): TPromise { + if (product.privacyStatementUrl) { + if (language) { + const queryArgChar = product.privacyStatementUrl.indexOf('?') > 0 ? '&' : '?'; + return TPromise.as(shell.openExternal(`${product.privacyStatementUrl}${queryArgChar}lang=${language}`)); + } else { + return TPromise.as(shell.openExternal(product.privacyStatementUrl)); + } + } + + + return TPromise.as(false); + } +} + +export class ShowAccessibilityOptionsAction extends Action { + + public static readonly ID = 'workbench.action.showAccessibilityOptions'; + public static LABEL = nls.localize('accessibilityOptions', "Accessibility Options"); + + constructor( + id: string, + label: string, + @IWindowsService private windowsService: IWindowsService + ) { + super(id, label); + } + + run(): TPromise { + return this.windowsService.openAccessibilityOptions(); + } +} + + export class ShowAboutDialogAction extends Action { public static readonly ID = 'workbench.action.showAboutDialog'; diff --git a/src/vs/workbench/electron-browser/main.contribution.ts b/src/vs/workbench/electron-browser/main.contribution.ts index 9e0b17e9720..71b38fef657 100644 --- a/src/vs/workbench/electron-browser/main.contribution.ts +++ b/src/vs/workbench/electron-browser/main.contribution.ts @@ -14,7 +14,7 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions, Configur import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; import { isWindows, isLinux, isMacintosh } from 'vs/base/common/platform'; -import { KeybindingsReferenceAction, OpenDocumentationUrlAction, OpenIntroductoryVideosUrlAction, OpenTipsAndTricksUrlAction, OpenIssueReporterAction, ReportPerformanceIssueUsingReporterAction, ZoomResetAction, ZoomOutAction, ZoomInAction, ToggleFullScreenAction, ToggleMenuBarAction, CloseWorkspaceAction, CloseCurrentWindowAction, SwitchWindow, NewWindowAction, NavigateUpAction, NavigateDownAction, NavigateLeftAction, NavigateRightAction, IncreaseViewSizeAction, DecreaseViewSizeAction, ShowStartupPerformance, ToggleSharedProcessAction, QuickSwitchWindow, QuickOpenRecentAction, inRecentFilesPickerContextKey, ShowAboutDialogAction, InspectContextKeysAction, OpenProcessExplorer } from 'vs/workbench/electron-browser/actions'; +import { KeybindingsReferenceAction, OpenDocumentationUrlAction, OpenIntroductoryVideosUrlAction, OpenTipsAndTricksUrlAction, OpenIssueReporterAction, ReportPerformanceIssueUsingReporterAction, ZoomResetAction, ZoomOutAction, ZoomInAction, ToggleFullScreenAction, ToggleMenuBarAction, CloseWorkspaceAction, CloseCurrentWindowAction, SwitchWindow, NewWindowAction, NavigateUpAction, NavigateDownAction, NavigateLeftAction, NavigateRightAction, IncreaseViewSizeAction, DecreaseViewSizeAction, ShowStartupPerformance, ToggleSharedProcessAction, QuickSwitchWindow, QuickOpenRecentAction, inRecentFilesPickerContextKey, ShowAboutDialogAction, InspectContextKeysAction, OpenProcessExplorer, OpenTwitterUrlAction, OpenRequestFeatureUrlAction, OpenPrivacyStatementUrlAction, OpenLicenseUrlAction, ShowAccessibilityOptionsAction } from 'vs/workbench/electron-browser/actions'; import { registerCommands } from 'vs/workbench/electron-browser/commands'; import { AddRootFolderAction, GlobalRemoveRootFolderAction, OpenWorkspaceAction, SaveWorkspaceAsAction, OpenWorkspaceConfigFileAction, DuplicateWorkspaceInNewWindowAction, OpenFileFolderAction, OpenFileAction, OpenFolderAction } from 'vs/workbench/browser/actions/workspaceActions'; import { ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -69,6 +69,17 @@ if (OpenTipsAndTricksUrlAction.AVAILABLE) { workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenTipsAndTricksUrlAction, OpenTipsAndTricksUrlAction.ID, OpenTipsAndTricksUrlAction.LABEL), 'Help: Tips and Tricks', helpCategory); } +workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenTwitterUrlAction, OpenTwitterUrlAction.ID, OpenTwitterUrlAction.LABEL), 'Help: Join us on Twitter', helpCategory); + +workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenRequestFeatureUrlAction, OpenRequestFeatureUrlAction.ID, + OpenRequestFeatureUrlAction.LABEL), 'Help: Search Feature Requests', helpCategory); + +workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenLicenseUrlAction, OpenLicenseUrlAction.ID, OpenLicenseUrlAction.LABEL), 'Help: View License', helpCategory); + +workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenPrivacyStatementUrlAction, OpenPrivacyStatementUrlAction.ID, OpenPrivacyStatementUrlAction.LABEL), 'Help: Privacy Statement', helpCategory); + +workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ShowAccessibilityOptionsAction, ShowAccessibilityOptionsAction.ID, ShowAccessibilityOptionsAction.LABEL), 'Help: Accessibility Options', helpCategory); + workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ShowAboutDialogAction, ShowAboutDialogAction.ID, ShowAboutDialogAction.LABEL), 'Help: About', helpCategory); workbenchActionsRegistry.registerWorkbenchAction( diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index 7348c180e15..b9e2dabcb74 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -1316,6 +1316,10 @@ export class TestWindowsService implements IWindowsService { return TPromise.as(void 0); } + openAccessibilityOptions(): TPromise { + return TPromise.as(void 0); + } + openAboutDialog(): TPromise { return TPromise.as(void 0); } From 8829182e4aa6dca37a6997a3be9604d09b540288 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 25 Jun 2018 16:25:57 -0700 Subject: [PATCH 062/228] Never apply an update path edit that tries doing something with node_modules Root cause is https://github.com/Microsoft/TypeScript/issues/24914 Fixes #52675 --- .../src/features/updatePathsOnRename.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/extensions/typescript-language-features/src/features/updatePathsOnRename.ts b/extensions/typescript-language-features/src/features/updatePathsOnRename.ts index 4e9c0398fea..e1c8ff89069 100644 --- a/extensions/typescript-language-features/src/features/updatePathsOnRename.ts +++ b/extensions/typescript-language-features/src/features/updatePathsOnRename.ts @@ -225,6 +225,12 @@ export class UpdateImportsOnFileRenameHandler { const edits: Proto.FileCodeEdits[] = []; for (const edit of response.body) { + // Workaround for https://github.com/Microsoft/vscode/issues/52675 + for (const change of (edit as Proto.FileCodeEdits).textChanges) { + if (change.newText.match(/\/node_modules\//gi)) { + continue; + } + } edits.push(await this.fixEdit(edit, isDirectoryRename, oldFile, newFile)); } return typeConverters.WorkspaceEdit.fromFileCodeEdits(this.client, edits); From 2523a160e009b1fe953cac78f901a45e16e91204 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Mon, 25 Jun 2018 16:29:48 -0700 Subject: [PATCH 063/228] supporting #51954 --- src/vs/workbench/browser/parts/menubar/menubarPart.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/browser/parts/menubar/menubarPart.ts b/src/vs/workbench/browser/parts/menubar/menubarPart.ts index ab49bdcac26..31a6686f187 100644 --- a/src/vs/workbench/browser/parts/menubar/menubarPart.ts +++ b/src/vs/workbench/browser/parts/menubar/menubarPart.ts @@ -76,10 +76,6 @@ export class MenubarPart extends Part { 'Help': nls.localize({ key: 'mHelp', comment: ['&& denotes a mnemonic'] }, "&&Help") }; - private mnemonics: { - [index: number]: number; - } = {}; - private focusedMenu: { index: number; holder: Builder; @@ -428,12 +424,10 @@ export class MenubarPart extends Part { let event = new StandardKeyboardEvent(e as KeyboardEvent); let eventHandled = true; - if (event.equals(KeyCode.LeftArrow)) { + if (event.equals(KeyCode.LeftArrow) || (event.shiftKey && event.keyCode === KeyCode.Tab)) { this.focusPrevious(); - } else if (event.equals(KeyCode.RightArrow)) { + } else if (event.equals(KeyCode.RightArrow) || event.equals(KeyCode.Tab)) { this.focusNext(); - } else if (event.altKey && event.keyCode && this.mnemonics[event.keyCode] !== undefined && !this.focusedMenu) { - this.toggleCustomMenu(this.mnemonics[event.keyCode]); } else { eventHandled = false; } From 3ec9470f073723cf6d08b3cf65715ee4bcbc59d1 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Mon, 25 Jun 2018 16:48:51 -0700 Subject: [PATCH 064/228] Add "Add to Recommendations (Workspace)" (#52837) * Add "Add to Recommendations (Worksapce)" in addition ot the existing "Add to Recommendations (Workspace Folder)" * Add commands to add extensons to unwanted recommendations --- .../electron-browser/extensionsActions.ts | 269 +++++++++++++----- 1 file changed, 203 insertions(+), 66 deletions(-) diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts index 4343a09af02..005c98ddec0 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts @@ -1906,7 +1906,8 @@ export class ConfigureRecommendedExtensionsCommandsContributor extends Disposabl private workspaceContextKey = new RawContextKey('workspaceRecommendations', true); private workspaceFolderContextKey = new RawContextKey('workspaceFolderRecommendations', true); - private addToRecommendationsContextKey = new RawContextKey('addToRecommendations', false); + private addToWorkspaceRecommendationsContextKey = new RawContextKey('addToWorkspaceRecommendations', false); + private addToWorkspaceFolderRecommendationsContextKey = new RawContextKey('addToWorkspaceFolderRecommendations', false); constructor( @IContextKeyService contextKeyService: IContextKeyService, @@ -1922,9 +1923,16 @@ export class ConfigureRecommendedExtensionsCommandsContributor extends Disposabl boundWorkspaceFolderContextKey.set(workspaceContextService.getWorkspace().folders.length > 0); this._register(workspaceContextService.onDidChangeWorkspaceFolders(() => boundWorkspaceFolderContextKey.set(workspaceContextService.getWorkspace().folders.length > 0))); - const boundAddToRecommendationsContextKey = this.addToRecommendationsContextKey.bindTo(contextKeyService); - boundAddToRecommendationsContextKey.set(editorService.activeEditor instanceof ExtensionsInput); - this._register(editorService.onDidActiveEditorChange(() => boundAddToRecommendationsContextKey.set(editorService.activeEditor instanceof ExtensionsInput))); + const boundAddToWorkspaceRecommendationsContextKey = this.addToWorkspaceRecommendationsContextKey.bindTo(contextKeyService); + boundAddToWorkspaceRecommendationsContextKey.set(editorService.activeEditor instanceof ExtensionsInput && workspaceContextService.getWorkbenchState() === WorkbenchState.WORKSPACE); + this._register(editorService.onDidActiveEditorChange(() => boundAddToWorkspaceRecommendationsContextKey.set( + editorService.activeEditor instanceof ExtensionsInput && workspaceContextService.getWorkbenchState() === WorkbenchState.WORKSPACE))); + this._register(workspaceContextService.onDidChangeWorkbenchState(() => boundAddToWorkspaceRecommendationsContextKey.set( + editorService.activeEditor instanceof ExtensionsInput && workspaceContextService.getWorkbenchState() === WorkbenchState.WORKSPACE))); + + const boundAddToWorkspaceFolderRecommendationsContextKey = this.addToWorkspaceFolderRecommendationsContextKey.bindTo(contextKeyService); + boundAddToWorkspaceFolderRecommendationsContextKey.set(editorService.activeEditor instanceof ExtensionsInput); + this._register(editorService.onDidActiveEditorChange(() => boundAddToWorkspaceFolderRecommendationsContextKey.set(editorService.activeEditor instanceof ExtensionsInput))); this.registerCommands(); } @@ -1952,22 +1960,58 @@ export class ConfigureRecommendedExtensionsCommandsContributor extends Disposabl when: this.workspaceFolderContextKey }); - CommandsRegistry.registerCommand(AddToWorkspaceRecommendationsAction.ID, serviceAccesor => { - serviceAccesor.get(IInstantiationService).createInstance(AddToWorkspaceRecommendationsAction, AddToWorkspaceRecommendationsAction.ID, AddToWorkspaceRecommendationsAction.LABEL).run(); + CommandsRegistry.registerCommand(AddToWorkspaceRecommendationsAction.ADD_ID, serviceAccessor => { + serviceAccessor.get(IInstantiationService) + .createInstance(AddToWorkspaceRecommendationsAction, AddToWorkspaceRecommendationsAction.ADD_ID, AddToWorkspaceRecommendationsAction.ADD_LABEL) + .run(AddToWorkspaceRecommendationsAction.ADD); }); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { - id: AddToWorkspaceRecommendationsAction.ID, - title: `${ExtensionsLabel}: ${AddToWorkspaceRecommendationsAction.LABEL}` + id: AddToWorkspaceRecommendationsAction.ADD_ID, + title: `${ExtensionsLabel}: ${AddToWorkspaceRecommendationsAction.ADD_LABEL}` }, - when: this.addToRecommendationsContextKey + when: this.addToWorkspaceRecommendationsContextKey + }); + + CommandsRegistry.registerCommand(AddToWorkspaceFolderRecommendationsAction.ADD_ID, serviceAccessor => { + serviceAccessor.get(IInstantiationService) + .createInstance(AddToWorkspaceFolderRecommendationsAction, AddToWorkspaceFolderRecommendationsAction.ADD_ID, AddToWorkspaceFolderRecommendationsAction.ADD_LABEL) + .run(AddToWorkspaceRecommendationsAction.ADD); + }); + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { + id: AddToWorkspaceFolderRecommendationsAction.ADD_ID, + title: `${ExtensionsLabel}: ${AddToWorkspaceFolderRecommendationsAction.ADD_LABEL}` + }, + when: this.addToWorkspaceFolderRecommendationsContextKey + }); + + CommandsRegistry.registerCommand(AddToWorkspaceRecommendationsAction.IGNORE_ID, serviceAccessor => { + serviceAccessor.get(IInstantiationService) + .createInstance(AddToWorkspaceRecommendationsAction, AddToWorkspaceRecommendationsAction.IGNORE_ID, AddToWorkspaceRecommendationsAction.IGNORE_LABEL) + .run(AddToWorkspaceRecommendationsAction.IGNORE); + }); + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { + id: AddToWorkspaceRecommendationsAction.IGNORE_ID, + title: `${ExtensionsLabel}: ${AddToWorkspaceRecommendationsAction.IGNORE_LABEL}` + }, + when: this.addToWorkspaceRecommendationsContextKey + }); + + CommandsRegistry.registerCommand(AddToWorkspaceFolderRecommendationsAction.IGNORE_ID, serviceAccessor => { + serviceAccessor.get(IInstantiationService) + .createInstance(AddToWorkspaceFolderRecommendationsAction, AddToWorkspaceFolderRecommendationsAction.IGNORE_ID, AddToWorkspaceFolderRecommendationsAction.IGNORE_LABEL) + .run(AddToWorkspaceRecommendationsAction.IGNORE); + }); + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { + id: AddToWorkspaceFolderRecommendationsAction.IGNORE_ID, + title: `${ExtensionsLabel}: ${AddToWorkspaceFolderRecommendationsAction.IGNORE_LABEL}` + }, + when: this.addToWorkspaceFolderRecommendationsContextKey }); } - -} - -interface IExtensionsContent { - recommendations: string[]; } export abstract class AbstractConfigureRecommendedExtensionsAction extends Action { @@ -2011,61 +2055,87 @@ export abstract class AbstractConfigureRecommendedExtensionsAction extends Actio })); } - protected addRecommendedExtensionToFolder(extensionsFileResource: URI, extensionId: string): TPromise { + protected addExtensionToWorkspaceConfig(workspaceConfigurationFile: URI, extensionId: string, shouldRecommend: boolean) { + return this.getOrUpdateWorkspaceConfigurationFile(workspaceConfigurationFile) + .then(content => { + const extensionIdLowerCase = extensionId.toLowerCase(); + const workspaceExtensionsConfigContent: IExtensionsConfigContent = (json.parse(content.value) || {})['extensions'] || {}; + let insertInto = shouldRecommend ? workspaceExtensionsConfigContent.recommendations || [] : workspaceExtensionsConfigContent.unwantedRecommendations || []; + let removeFrom = shouldRecommend ? workspaceExtensionsConfigContent.unwantedRecommendations || [] : workspaceExtensionsConfigContent.recommendations || []; + + if (insertInto.some(e => e.toLowerCase() === extensionIdLowerCase)) { + return TPromise.as(null); + } + + insertInto.push(extensionId); + removeFrom = removeFrom.filter(x => x.toLowerCase() !== extensionIdLowerCase); + + return this.jsonEditingService.write(workspaceConfigurationFile, + { + key: 'extensions', + value: { + recommendations: shouldRecommend ? insertInto : removeFrom, + unwantedRecommendations: shouldRecommend ? removeFrom : insertInto + } + }, + true); + }); + } + + protected addExtensionToWorkspaceFolderConfig(extensionsFileResource: URI, extensionId: string, shouldRecommend: boolean): TPromise { return this.getOrCreateExtensionsFile(extensionsFileResource) .then(({ content }) => { const extensionIdLowerCase = extensionId.toLowerCase(); - const jsonContent: IExtensionsConfigContent = json.parse(content) || {}; - const folderRecommendations = jsonContent.recommendations || []; + const extensionsConfigContent: IExtensionsConfigContent = json.parse(content) || {}; + let insertInto = shouldRecommend ? extensionsConfigContent.recommendations || [] : extensionsConfigContent.unwantedRecommendations || []; + let removeFrom = shouldRecommend ? extensionsConfigContent.unwantedRecommendations || [] : extensionsConfigContent.recommendations || []; - if (folderRecommendations.some(e => e.toLowerCase() === extensionIdLowerCase)) { + if (insertInto.some(e => e.toLowerCase() === extensionIdLowerCase)) { return TPromise.as(null); } - folderRecommendations.push(extensionId); - const folderUnwantedRecommedations = jsonContent.unwantedRecommendations || []; - let index = -1; - for (let i = 0; i < folderUnwantedRecommedations.length; i++) { - if (folderUnwantedRecommedations[i].toLowerCase() === extensionIdLowerCase) { - index = i; - break; - } - } + insertInto.push(extensionId); - let removeFromUnwantedPromise = TPromise.wrap(null); - if (index > -1) { - folderUnwantedRecommedations.splice(index, 1); - removeFromUnwantedPromise = this.jsonEditingService.write(extensionsFileResource, + let removeFromPromise = TPromise.wrap(null); + if (removeFrom.some(e => e.toLowerCase() === extensionIdLowerCase)) { + removeFrom = removeFrom.filter(x => x.toLowerCase() !== extensionIdLowerCase); + removeFromPromise = this.jsonEditingService.write(extensionsFileResource, { - key: 'unwantedRecommendations', - value: folderUnwantedRecommedations + key: shouldRecommend ? 'unwantedRecommendations' : 'recommendations', + value: removeFrom }, true); } - return removeFromUnwantedPromise.then(() => + return removeFromPromise.then(() => this.jsonEditingService.write(extensionsFileResource, { - key: 'recommendations', - value: folderRecommendations + key: shouldRecommend ? 'recommendations' : 'unwantedRecommendations', + value: insertInto }, true) ); }); } - protected getFolderRecommendedExtensions(extensionsFileResource: URI): TPromise { + protected getWorkspaceExtensionsConfigContent(extensionsFileResource: URI): TPromise { return this.fileService.resolveContent(extensionsFileResource) .then(content => { - const folderRecommendations = (json.parse(content.value)); - return folderRecommendations.recommendations || []; - }, err => []); + return (json.parse(content.value) || {})['extensions'] || {}; + }, err => ({ recommendations: [], unwantedRecommendations: [] })); + } + + protected getWorkspaceFolderExtensionsConfigContent(extensionsFileResource: URI): TPromise { + return this.fileService.resolveContent(extensionsFileResource) + .then(content => { + return (json.parse(content.value)); + }, err => ({ recommendations: [], unwantedRecommendations: [] })); } private getOrUpdateWorkspaceConfigurationFile(workspaceConfigurationFile: URI): TPromise { return this.fileService.resolveContent(workspaceConfigurationFile) .then(content => { - const workspaceRecommendations = json.parse(content.value)['extensions']; + const workspaceRecommendations = json.parse(content.value)['extensions']; if (!workspaceRecommendations || !workspaceRecommendations.recommendations) { return this.jsonEditingService.write(workspaceConfigurationFile, { key: 'extensions', value: { recommendations: [] } }, true) .then(() => this.fileService.resolveContent(workspaceConfigurationFile)); @@ -2192,10 +2262,13 @@ export class ConfigureWorkspaceFolderRecommendedExtensionsAction extends Abstrac } } -export class AddToWorkspaceRecommendationsAction extends AbstractConfigureRecommendedExtensionsAction { - - static readonly ID = 'workbench.extensions.action.addToWorkspaceRecommendations'; - static LABEL = localize('addToWorkspaceRecommendations', "Add to workspace recommendations"); +export class AddToWorkspaceFolderRecommendationsAction extends AbstractConfigureRecommendedExtensionsAction { + static readonly ADD = true; + static readonly IGNORE = false; + static readonly ADD_ID = 'workbench.extensions.action.addToWorkspaceFolderRecommendations'; + static readonly ADD_LABEL = localize('addToWorkspaceFolderRecommendations', "Add to Recommended Extensions (Workspace Folder)"); + static readonly IGNORE_ID = 'workbench.extensions.action.addToWorkspaceFolderIgnoredRecommendations'; + static readonly IGNORE_LABEL = localize('addToWorkspaceFolderIgnoredRecommendations', "Ignore Recommended Extension (Workspace Folder)"); constructor( id: string, @@ -2208,24 +2281,16 @@ export class AddToWorkspaceRecommendationsAction extends AbstractConfigureRecomm @ICommandService private commandService: ICommandService, @INotificationService private notificationService: INotificationService ) { - super( - id, - label, - contextService, - fileService, - editorService, - jsonEditingService, - textModelResolverService - ); + super(id, label, contextService, fileService, editorService, jsonEditingService, textModelResolverService); } - run(): TPromise { + run(shouldRecommend: boolean): TPromise { if (!(this.editorService.activeEditor instanceof ExtensionsInput) || !this.editorService.activeEditor.extension) { return TPromise.as(null); } const folders = this.contextService.getWorkspace().folders; if (!folders || !folders.length) { - this.notificationService.info(localize('AddToWorkspaceRecommendations.noWorkspace', 'There is no workspace open to add recommendations.')); + this.notificationService.info(localize('AddToWorkspaceFolderRecommendations.noWorkspace', 'There are no workspace folders open to add recommendations.')); return TPromise.as(null); } @@ -2239,23 +2304,95 @@ export class AddToWorkspaceRecommendationsAction extends AbstractConfigureRecomm return TPromise.as(null); } const configurationFile = workspaceFolder.toResource(paths.join('.vscode', 'extensions.json')); - return this.getFolderRecommendedExtensions(configurationFile).then(recommendations => { + return this.getWorkspaceFolderExtensionsConfigContent(configurationFile).then(content => { const extensionIdLowerCase = extensionId.toLowerCase(); - if (recommendations.some(e => e.toLowerCase() === extensionIdLowerCase)) { - this.notificationService.info(localize('AddToWorkspaceRecommendations.alreadyExists', 'This extension is already present in workspace recommendations.')); - return TPromise.as(null); - } + if (shouldRecommend) { + if (content.recommendations.some(e => e.toLowerCase() === extensionIdLowerCase)) { + this.notificationService.info(localize('AddToWorkspaceFolderRecommendations.alreadyExists', 'This extension is already present in this workspace folder\'s recommendations.')); + return TPromise.as(null); + } - return this.addRecommendedExtensionToFolder(configurationFile, extensionId).then(() => { - this.notificationService.info(localize('AddToWorkspaceRecommendations.success', 'The extension was successfully added to workspace recommendations.')); - }, err => { - this.notificationService.error(localize('AddToWorkspaceRecommendations.failure', 'Failed to write to extensions.json. {0}', err)); - }); + return this.addExtensionToWorkspaceFolderConfig(configurationFile, extensionId, shouldRecommend).then(() => { + this.notificationService.info(localize('AddToWorkspaceFolderRecommendations.success', 'The extension was successfully added to this workspace folder\'s recommendations.')); + }, err => { + this.notificationService.error(localize('AddToWorkspaceFolderRecommendations.failure', 'Failed to write to extensions.json. {0}', err)); + }); + } + else { + if (content.unwantedRecommendations.some(e => e.toLowerCase() === extensionIdLowerCase)) { + this.notificationService.info(localize('AddToWorkspaceFolderIgnoredRecommendations.alreadyExists', 'This extension is already present in this workspace folder\'s unwanted recommendations.')); + return TPromise.as(null); + } + + return this.addExtensionToWorkspaceFolderConfig(configurationFile, extensionId, shouldRecommend).then(() => { + this.notificationService.info(localize('AddToWorkspaceFolderIgnoredRecommendations.success', 'The extension was successfully added to this workspace folder\'s unwanted recommendations.')); + }, err => { + this.notificationService.error(localize('AddToWorkspaceFolderRecommendations.failure', 'Failed to write to extensions.json. {0}', err)); + }); + } }); }); } } +export class AddToWorkspaceRecommendationsAction extends AbstractConfigureRecommendedExtensionsAction { + static readonly ADD = true; + static readonly IGNORE = false; + static readonly ADD_ID = 'workbench.extensions.action.addToWorkspaceRecommendations'; + static readonly ADD_LABEL = localize('addToWorkspaceRecommendations', "Add to Recommended Extensions (Workspace)"); + static readonly IGNORE_ID = 'workbench.extensions.action.addToWorkspaceIgnoredRecommendations'; + static readonly IGNORE_LABEL = localize('addToWorkspaceIgnoredRecommendations', "Ignore Recommended Extension (Workspace)"); + + constructor( + id: string, + label: string, + @IFileService fileService: IFileService, + @IWorkspaceContextService contextService: IWorkspaceContextService, + @IEditorService editorService: IEditorService, + @IJSONEditingService jsonEditingService: IJSONEditingService, + @ITextModelService textModelResolverService: ITextModelService, + @INotificationService private notificationService: INotificationService + ) { + super(id, label, contextService, fileService, editorService, jsonEditingService, textModelResolverService); + } + + run(shouldRecommend: boolean): TPromise { + if (!(this.editorService.activeEditor instanceof ExtensionsInput) || !this.editorService.activeEditor.extension) { + return TPromise.as(null); + } + const workspaceConfig = this.contextService.getWorkspace().configuration; + + const extensionId = this.editorService.activeEditor.extension.id; + + return this.getWorkspaceExtensionsConfigContent(workspaceConfig).then(content => { + const extensionIdLowerCase = extensionId.toLowerCase(); + if (shouldRecommend) { + if ((content.recommendations || []).some(e => e.toLowerCase() === extensionIdLowerCase)) { + this.notificationService.info(localize('AddToWorkspaceRecommendations.alreadyExists', 'This extension is already present in workspace recommendations.')); + return TPromise.as(null); + } + + return this.addExtensionToWorkspaceConfig(workspaceConfig, extensionId, shouldRecommend).then(() => { + this.notificationService.info(localize('AddToWorkspaceRecommendations.success', 'The extension was successfully added to this workspace\'s recommendations.')); + }, err => { + this.notificationService.error(localize('AddToWorkspaceRecommendations.failure', 'Failed to write. {0}', err)); + }); + } else { + if ((content.unwantedRecommendations || []).some(e => e.toLowerCase() === extensionIdLowerCase)) { + this.notificationService.info(localize('AddToWorkspaceUnwantedRecommendations.alreadyExists', 'This extension is already present in workspace unwanted recommendations.')); + return TPromise.as(null); + } + + return this.addExtensionToWorkspaceConfig(workspaceConfig, extensionId, shouldRecommend).then(() => { + this.notificationService.info(localize('AddToWorkspaceUnwantedRecommendations.success', 'The extension was successfully added to this workspace\'s unwanted recommendations.')); + }, err => { + this.notificationService.error(localize('AddToWorkspaceRecommendations.failure', 'Failed to write. {0}', err)); + }); + } + }); + } +} + export class MaliciousStatusLabelAction extends Action { private static readonly Class = 'malicious-status'; From 065a1000522d1d8c5d5a54b12e3dcfce2f386cf8 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 25 Jun 2018 16:27:18 -0700 Subject: [PATCH 065/228] #52815 - Better settings editor KB navigation --- .../preferences/browser/settingsEditor2.ts | 40 ++++++++++++++----- .../parts/preferences/browser/settingsTree.ts | 30 ++++++++------ .../parts/preferences/browser/tocTree.ts | 4 +- .../parts/preferences/common/preferences.ts | 3 ++ .../preferences.contribution.ts | 30 +++++++++++++- 5 files changed, 80 insertions(+), 27 deletions(-) diff --git a/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts b/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts index 3b886e64f7a..2975d4299b2 100644 --- a/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts @@ -11,7 +11,6 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import * as collections from 'vs/base/common/collections'; import { Color } from 'vs/base/common/color'; import { getErrorMessage, isPromiseCanceledError } from 'vs/base/common/errors'; -import { KeyCode } from 'vs/base/common/keyCodes'; import URI from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import { ITree, ITreeConfiguration } from 'vs/base/parts/tree/browser/tree'; @@ -34,7 +33,7 @@ import { SearchWidget, SettingsTarget, SettingsTargetsWidget } from 'vs/workbenc import { commonlyUsedData, tocData } from 'vs/workbench/parts/preferences/browser/settingsLayout'; import { ISettingsEditorViewState, NonExpandableTree, resolveExtensionsSettings, resolveSettingsTree, SearchResultIdx, SearchResultModel, SettingsAccessibilityProvider, SettingsDataSource, SettingsRenderer, SettingsTreeController, SettingsTreeElement, SettingsTreeFilter, SettingsTreeGroupElement, SettingsTreeModel, SettingsTreeSettingElement } from 'vs/workbench/parts/preferences/browser/settingsTree'; import { TOCDataSource, TOCRenderer, TOCTreeModel } from 'vs/workbench/parts/preferences/browser/tocTree'; -import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_SEARCH_FOCUS, IPreferencesSearchService, ISearchProvider } from 'vs/workbench/parts/preferences/common/preferences'; +import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_FIRST_ROW_FOCUS, CONTEXT_SETTINGS_SEARCH_FOCUS, IPreferencesSearchService, ISearchProvider } from 'vs/workbench/parts/preferences/common/preferences'; import { IPreferencesService, ISearchResult, ISettingsEditorModel } from 'vs/workbench/services/preferences/common/preferences'; import { SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput'; import { DefaultSettingsEditorModel } from 'vs/workbench/services/preferences/common/preferencesModels'; @@ -75,6 +74,8 @@ export class SettingsEditor2 extends BaseEditor { private viewState: ISettingsEditorViewState; private searchResultModel: SearchResultModel; + + private firstRowFocused: IContextKey; private inSettingsEditorContextKey: IContextKey; private searchFocusContextKey: IContextKey; @@ -99,6 +100,7 @@ export class SettingsEditor2 extends BaseEditor { this.inSettingsEditorContextKey = CONTEXT_SETTINGS_EDITOR.bindTo(contextKeyService); this.searchFocusContextKey = CONTEXT_SETTINGS_SEARCH_FOCUS.bindTo(contextKeyService); + this.firstRowFocused = CONTEXT_SETTINGS_FIRST_ROW_FOCUS.bindTo(contextKeyService); this._register(configurationService.onDidChangeConfiguration(e => { this.onConfigUpdate(); @@ -138,6 +140,17 @@ export class SettingsEditor2 extends BaseEditor { this.focusSearch(); } + focusSettings(): void { + const selection = this.settingsTree.getSelection(); + if (selection && selection[0]) { + this.settingsTree.setFocus(selection[0]); + } else { + this.settingsTree.focusFirst(); + } + + this.settingsTree.domFocus(); + } + focusSearch(): void { this.searchWidget.focus(); } @@ -164,12 +177,6 @@ export class SettingsEditor2 extends BaseEditor { focusKey: this.searchFocusContextKey })); this._register(this.searchWidget.onDidChange(() => this.onSearchInputChanged())); - this._register(DOM.addStandardDisposableListener(this.searchWidget.domNode, 'keydown', e => { - if (e.keyCode === KeyCode.DownArrow) { - this.settingsTree.focusFirst(); - this.settingsTree.domFocus(); - } - })); const advancedCustomization = DOM.append(this.headerContainer, $('.settings-advanced-customization')); const advancedCustomizationLabel = DOM.append(advancedCustomization, $('span.settings-advanced-customization-label')); @@ -344,8 +351,21 @@ export class SettingsEditor2 extends BaseEditor { this.selectedElement = e.focus; })); - this._register(this.settingsTree.onDidChangeSelection(() => { + this._register(this.settingsTree.onDidChangeSelection(e => { this.updateTreeScrollSync(); + + let firstRowFocused = false; + const selection: SettingsTreeElement = e.selection[0]; + if (selection) { + if (this.searchResultModel) { + firstRowFocused = selection.id === this.searchResultModel.getChildren()[0].id; + } else { + const firstRowId = this.settingsTreeModel.root.children[0] && this.settingsTreeModel.root.children[0].id; + firstRowFocused = selection.id === firstRowId; + } + } + + this.firstRowFocused.set(firstRowFocused); })); this._register(this.settingsTree.onDidScroll(() => { @@ -683,7 +703,7 @@ export class SettingsEditor2 extends BaseEditor { const [result] = results; if (!this.searchResultModel) { - this.searchResultModel = new SearchResultModel(); + this.searchResultModel = this.instantiationService.createInstance(SearchResultModel, this.viewState); this.tocTreeModel.currentSearchModel = this.searchResultModel; this.toggleSearchMode(); this.settingsTree.setInput(this.searchResultModel); diff --git a/src/vs/workbench/parts/preferences/browser/settingsTree.ts b/src/vs/workbench/parts/preferences/browser/settingsTree.ts index 9ed6666a967..c3d2c30f77d 100644 --- a/src/vs/workbench/parts/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/parts/preferences/browser/settingsTree.ts @@ -105,7 +105,7 @@ export class SettingsTreeModel { this.update(this._tocRoot); } - get root(): SettingsTreeElement { + get root(): SettingsTreeGroupElement { return this._root; } @@ -286,11 +286,6 @@ function getFlatSettings(settingsGroups: ISettingsGroup[]) { export class SettingsDataSource implements IDataSource { - constructor( - private viewState: ISettingsEditorViewState, - @IConfigurationService private configurationService: IConfigurationService - ) { } - getId(tree: ITree, element: SettingsTreeElement): string { return element.id; } @@ -307,18 +302,13 @@ export class SettingsDataSource implements IDataSource { return false; } - private getSearchResultChildren(searchResult: SearchResultModel): SettingsTreeSettingElement[] { - return searchResult.getFlatSettings() - .map(s => createSettingsTreeSettingElement(s, searchResult, this.viewState.settingsTarget, this.configurationService)); - } - getChildren(tree: ITree, element: SettingsTreeElement): TPromise { return TPromise.as(this._getChildren(element)); } private _getChildren(element: SettingsTreeElement): SettingsTreeElement[] { if (element instanceof SearchResultModel) { - return this.getSearchResultChildren(element); + return element.getChildren(); } else if (element instanceof SettingsTreeGroupElement) { return element.children; } else { @@ -866,9 +856,19 @@ export enum SearchResultIdx { export class SearchResultModel { private rawSearchResults: ISearchResult[]; private cachedUniqueSearchResults: ISearchResult[]; + private children: SettingsTreeSettingElement[]; readonly id = 'searchResultModel'; + constructor( + private _viewState: ISettingsEditorViewState, + @IConfigurationService private _configurationService: IConfigurationService + ) { } + + getChildren(): SettingsTreeSettingElement[] { + return this.children; + } + getUniqueResults(): ISearchResult[] { if (this.cachedUniqueSearchResults) { return this.cachedUniqueSearchResults; @@ -901,9 +901,13 @@ export class SearchResultModel { this.cachedUniqueSearchResults = null; this.rawSearchResults = this.rawSearchResults || []; this.rawSearchResults[type] = result; + + // Recompute children + this.children = this.getFlatSettings() + .map(s => createSettingsTreeSettingElement(s, result, this._viewState.settingsTarget, this._configurationService)); } - getFlatSettings(): ISetting[] { + private getFlatSettings(): ISetting[] { const flatSettings: ISetting[] = []; this.getUniqueResults() .filter(r => !!r) diff --git a/src/vs/workbench/parts/preferences/browser/tocTree.ts b/src/vs/workbench/parts/preferences/browser/tocTree.ts index f463c6e3d7a..54c4d4297e8 100644 --- a/src/vs/workbench/parts/preferences/browser/tocTree.ts +++ b/src/vs/workbench/parts/preferences/browser/tocTree.ts @@ -48,8 +48,8 @@ export class TOCTreeModel { } private getSearchResultChildrenCount(group: SettingsTreeGroupElement): number { - return this._currentSearchModel.getFlatSettings().filter(s => { - return this.groupContainsSetting(group, s); + return this._currentSearchModel.getChildren().filter(child => { + return this.groupContainsSetting(group, child.setting); }).length; } diff --git a/src/vs/workbench/parts/preferences/common/preferences.ts b/src/vs/workbench/parts/preferences/common/preferences.ts index fdf684a9d5a..62c8bada9a7 100644 --- a/src/vs/workbench/parts/preferences/common/preferences.ts +++ b/src/vs/workbench/parts/preferences/common/preferences.ts @@ -60,6 +60,7 @@ export interface IKeybindingsEditor extends IEditor { export const CONTEXT_SETTINGS_EDITOR = new RawContextKey('inSettingsEditor', false); export const CONTEXT_SETTINGS_SEARCH_FOCUS = new RawContextKey('inSettingsSearch', false); +export const CONTEXT_SETTINGS_FIRST_ROW_FOCUS = new RawContextKey('firstSettingRowFocused', false); export const CONTEXT_KEYBINDINGS_EDITOR = new RawContextKey('inKeybindings', false); export const CONTEXT_KEYBINDINGS_SEARCH_FOCUS = new RawContextKey('inKeybindingsSearch', false); export const CONTEXT_KEYBINDING_FOCUS = new RawContextKey('keybindingFocus', false); @@ -70,6 +71,8 @@ export const SETTINGS_EDITOR_COMMAND_FOCUS_NEXT_SETTING = 'settings.action.focus export const SETTINGS_EDITOR_COMMAND_FOCUS_PREVIOUS_SETTING = 'settings.action.focusPreviousSetting'; export const SETTINGS_EDITOR_COMMAND_FOCUS_FILE = 'settings.action.focusSettingsFile'; export const SETTINGS_EDITOR_COMMAND_EDIT_FOCUSED_SETTING = 'settings.action.editFocusedSetting'; +export const SETTINGS_EDITOR_COMMAND_FOCUS_SEARCH_FROM_SETTINGS = 'settings.action.focusSearchFromSettings'; +export const SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_FROM_SEARCH = 'settings.action.focusSettingsFromSearch'; export const KEYBINDINGS_EDITOR_COMMAND_SEARCH = 'keybindings.editor.searchKeybindings'; export const KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS = 'keybindings.editor.clearSearchResults'; export const KEYBINDINGS_EDITOR_COMMAND_DEFINE = 'keybindings.editor.defineKeybinding'; diff --git a/src/vs/workbench/parts/preferences/electron-browser/preferences.contribution.ts b/src/vs/workbench/parts/preferences/electron-browser/preferences.contribution.ts index 08c8d92da5c..7c0c26e82a7 100644 --- a/src/vs/workbench/parts/preferences/electron-browser/preferences.contribution.ts +++ b/src/vs/workbench/parts/preferences/electron-browser/preferences.contribution.ts @@ -22,7 +22,7 @@ import { KeybindingsEditor } from 'vs/workbench/parts/preferences/browser/keybin import { OpenRawDefaultSettingsAction, OpenSettingsAction, OpenGlobalSettingsAction, OpenGlobalKeybindingsFileAction, OpenWorkspaceSettingsAction, OpenFolderSettingsAction, ConfigureLanguageBasedSettingsAction, OPEN_FOLDER_SETTINGS_COMMAND, OpenGlobalKeybindingsAction, OpenSettings2Action } from 'vs/workbench/parts/preferences/browser/preferencesActions'; import { IKeybindingsEditor, IPreferencesSearchService, CONTEXT_KEYBINDING_FOCUS, CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS, KEYBINDINGS_EDITOR_COMMAND_DEFINE, KEYBINDINGS_EDITOR_COMMAND_REMOVE, KEYBINDINGS_EDITOR_COMMAND_SEARCH, - KEYBINDINGS_EDITOR_COMMAND_COPY, KEYBINDINGS_EDITOR_COMMAND_RESET, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND, KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR, KEYBINDINGS_EDITOR_COMMAND_FOCUS_KEYBINDINGS, KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_SEARCH, CONTEXT_SETTINGS_EDITOR, SETTINGS_EDITOR_COMMAND_FOCUS_FILE, CONTEXT_SETTINGS_SEARCH_FOCUS, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_FOCUS_NEXT_SETTING, SETTINGS_EDITOR_COMMAND_FOCUS_PREVIOUS_SETTING, SETTINGS_EDITOR_COMMAND_EDIT_FOCUSED_SETTING + KEYBINDINGS_EDITOR_COMMAND_COPY, KEYBINDINGS_EDITOR_COMMAND_RESET, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND, KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR, KEYBINDINGS_EDITOR_COMMAND_FOCUS_KEYBINDINGS, KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_SEARCH, CONTEXT_SETTINGS_EDITOR, SETTINGS_EDITOR_COMMAND_FOCUS_FILE, CONTEXT_SETTINGS_SEARCH_FOCUS, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_FOCUS_NEXT_SETTING, SETTINGS_EDITOR_COMMAND_FOCUS_PREVIOUS_SETTING, SETTINGS_EDITOR_COMMAND_EDIT_FOCUSED_SETTING, SETTINGS_EDITOR_COMMAND_FOCUS_SEARCH_FROM_SETTINGS, SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_FROM_SEARCH, CONTEXT_SETTINGS_FIRST_ROW_FOCUS } from 'vs/workbench/parts/preferences/common/preferences'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; @@ -355,6 +355,24 @@ const startSearchCommand = new StartSearchDefaultSettingsCommand({ }); KeybindingsRegistry.registerCommandAndKeybindingRule(startSearchCommand.toCommandAndKeybindingRule(KeybindingsRegistry.WEIGHT.editorContrib())); +class FocusSearchFromSettingsCommand extends SettingsCommand { + + public runCommand(accessor: ServicesAccessor, args: any): void { + const preferencesEditor = this.getPreferencesEditor(accessor); + if (preferencesEditor) { + preferencesEditor.focusSearch(); + } + } + +} +const focusSearchFromSettingsCommand = new FocusSearchFromSettingsCommand({ + id: SETTINGS_EDITOR_COMMAND_FOCUS_SEARCH_FROM_SETTINGS, + precondition: ContextKeyExpr.and(CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_FIRST_ROW_FOCUS), + kbOpts: { primary: KeyCode.UpArrow } +}); +KeybindingsRegistry.registerCommandAndKeybindingRule(focusSearchFromSettingsCommand.toCommandAndKeybindingRule(KeybindingsRegistry.WEIGHT.workbenchContrib())); + + class ClearSearchResultsCommand extends SettingsCommand { public runCommand(accessor: ServicesAccessor, args: any): void { @@ -378,9 +396,10 @@ class FocusSettingsFileEditorCommand extends SettingsCommand { const preferencesEditor = this.getPreferencesEditor(accessor); if (preferencesEditor instanceof PreferencesEditor) { preferencesEditor.focusSettingsFileEditor(); + } else { + preferencesEditor.focusSettings(); } } - } const focusSettingsFileEditorCommand = new FocusSettingsFileEditorCommand({ id: SETTINGS_EDITOR_COMMAND_FOCUS_FILE, @@ -389,6 +408,13 @@ const focusSettingsFileEditorCommand = new FocusSettingsFileEditorCommand({ }); KeybindingsRegistry.registerCommandAndKeybindingRule(focusSettingsFileEditorCommand.toCommandAndKeybindingRule(KeybindingsRegistry.WEIGHT.editorContrib())); +const focusSettingsFromSearchCommand = new FocusSettingsFileEditorCommand({ + id: SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_FROM_SEARCH, + precondition: CONTEXT_SETTINGS_SEARCH_FOCUS, + kbOpts: { primary: KeyCode.DownArrow } +}); +KeybindingsRegistry.registerCommandAndKeybindingRule(focusSettingsFromSearchCommand.toCommandAndKeybindingRule(KeybindingsRegistry.WEIGHT.workbenchContrib())); + class FocusNextSearchResultCommand extends SettingsCommand { public runCommand(accessor: ServicesAccessor, args: any): void { From 082918976ec2e8023ab21393f090ba0dd0b85fa7 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 25 Jun 2018 16:59:18 -0700 Subject: [PATCH 066/228] #52815 - Settings editor - 'enter' edits setting --- .../preferences/browser/settingsEditor2.ts | 28 +++++++++++++------ .../preferences.contribution.ts | 18 ++++++++++++ 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts b/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts index 2975d4299b2..8726d796de5 100644 --- a/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts @@ -155,6 +155,14 @@ export class SettingsEditor2 extends BaseEditor { this.searchWidget.focus(); } + editSelectedSetting(): void { + const focus = this.settingsTree.getFocus(); + if (focus instanceof SettingsTreeSettingElement) { + const itemId = focus.id.replace(/\./g, '_'); + this.focusEditControlForRow(itemId); + } + } + clearSearchResults(): void { this.searchWidget.clear(); } @@ -585,14 +593,7 @@ export class SettingsEditor2 extends BaseEditor { return this.settingsTree.refresh() .then(() => { if (focusedRowId) { - const rowSelector = `.setting-item#${focusedRowId}`; - const inputElementToFocus: HTMLElement = this.settingsTreeContainer.querySelector(`${rowSelector} input, ${rowSelector} select, ${rowSelector} a, ${rowSelector} .monaco-custom-checkbox`); - if (inputElementToFocus) { - inputElementToFocus.focus(); - if (typeof selection === 'number') { - (inputElementToFocus).setSelectionRange(selection, selection); - } - } + this.focusEditControlForRow(focusedRowId, selection); } }) .then(() => { @@ -600,6 +601,17 @@ export class SettingsEditor2 extends BaseEditor { }); } + private focusEditControlForRow(id: string, selection?: number): void { + const rowSelector = `.setting-item#${id}`; + const inputElementToFocus: HTMLElement = this.settingsTreeContainer.querySelector(`${rowSelector} input, ${rowSelector} select, ${rowSelector} a, ${rowSelector} .monaco-custom-checkbox`); + if (inputElementToFocus) { + inputElementToFocus.focus(); + if (typeof selection === 'number') { + (inputElementToFocus).setSelectionRange(selection, selection); + } + } + } + private onSearchInputChanged(): void { const query = this.searchWidget.getValue().trim(); this.delayedFilterLogging.cancel(); diff --git a/src/vs/workbench/parts/preferences/electron-browser/preferences.contribution.ts b/src/vs/workbench/parts/preferences/electron-browser/preferences.contribution.ts index 7c0c26e82a7..367346b7259 100644 --- a/src/vs/workbench/parts/preferences/electron-browser/preferences.contribution.ts +++ b/src/vs/workbench/parts/preferences/electron-browser/preferences.contribution.ts @@ -465,3 +465,21 @@ const editFocusedSettingCommand = new EditFocusedSettingCommand({ kbOpts: { primary: KeyMod.CtrlCmd | KeyCode.US_DOT } }); KeybindingsRegistry.registerCommandAndKeybindingRule(editFocusedSettingCommand.toCommandAndKeybindingRule(KeybindingsRegistry.WEIGHT.editorContrib())); + +class EditFocusedSettingCommand2 extends SettingsCommand { + + public runCommand(accessor: ServicesAccessor, args: any): void { + const preferencesEditor = this.getPreferencesEditor(accessor); + if (preferencesEditor instanceof SettingsEditor2) { + preferencesEditor.editSelectedSetting(); + } + } + +} + +const editFocusedSettingCommand2 = new EditFocusedSettingCommand2({ + id: SETTINGS_EDITOR_COMMAND_EDIT_FOCUSED_SETTING, + precondition: CONTEXT_SETTINGS_EDITOR, + kbOpts: { primary: KeyCode.Enter } +}); +KeybindingsRegistry.registerCommandAndKeybindingRule(editFocusedSettingCommand2.toCommandAndKeybindingRule(KeybindingsRegistry.WEIGHT.workbenchContrib())); From e62554e4b19a6bf4dc35273e84603d4b1cb22ef0 Mon Sep 17 00:00:00 2001 From: Pine Wu Date: Mon, 25 Jun 2018 17:26:32 -0700 Subject: [PATCH 067/228] [css] Update grammar from octref/language-css --- extensions/css/package.json | 2 +- extensions/css/syntaxes/css.tmLanguage.json | 45 +++++++++++++++++++-- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/extensions/css/package.json b/extensions/css/package.json index 9517029a69f..13f737e92fc 100644 --- a/extensions/css/package.json +++ b/extensions/css/package.json @@ -8,7 +8,7 @@ "vscode": "0.10.x" }, "scripts": { - "update-grammar": "node ../../build/npm/update-grammar.js atom/language-css grammars/css.cson ./syntaxes/css.tmLanguage.json" + "update-grammar": "node ../../build/npm/update-grammar.js octref/language-css grammars/css.cson ./syntaxes/css.tmLanguage.json" }, "contributes": { "languages": [ diff --git a/extensions/css/syntaxes/css.tmLanguage.json b/extensions/css/syntaxes/css.tmLanguage.json index d1be44fc3d9..ec7659719bb 100644 --- a/extensions/css/syntaxes/css.tmLanguage.json +++ b/extensions/css/syntaxes/css.tmLanguage.json @@ -1,10 +1,10 @@ { "information_for_contributors": [ - "This file has been converted from https://github.com/atom/language-css/blob/master/grammars/css.cson", + "This file has been converted from https://github.com/octref/language-css/blob/master/grammars/css.cson", "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/atom/language-css/commit/17ad55bc5f65c16585e80ea1c7c19e0c0814f6d5", + "version": "https://github.com/octref/language-css/commit/ea1d7e3619966e47c57498913a5eabea0cce7538", "name": "CSS", "scopeName": "source.css", "patterns": [ @@ -604,6 +604,45 @@ "include": "#string" } ] + }, + { + "begin": "(?i)(?=@[\\w-]+(\\s|\\(|/\\*|$))", + "end": "(?<=})(?!\\G)", + "patterns": [ + { + "begin": "(?i)\\G(@)[\\w-]+", + "beginCaptures": { + "0": { + "name": "keyword.control.at-rule.css" + }, + "1": { + "name": "punctuation.definition.keyword.css" + } + }, + "end": "(?=\\s*[{;])", + "name": "meta.at-rule.header.css" + }, + { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.section.begin.bracket.curly.css" + } + }, + "end": "}", + "endCaptures": { + "0": { + "name": "punctuation.section.end.bracket.curly.css" + } + }, + "name": "meta.at-rule.body.css", + "patterns": [ + { + "include": "$self" + } + ] + } + ] } ] }, @@ -1411,7 +1450,7 @@ "name": "invalid.illegal.colon.css" } }, - "match": "(?xi)\n(:)(:*)\n(?: active|any-link|checked|default|disabled|empty|enabled|first\n | (?:first|last|only)-(?:child|of-type)|focus|focus-within|fullscreen|host|hover\n | in-range|indeterminate|invalid|left|link|optional|out-of-range\n | read-only|read-write|required|right|root|scope|target|unresolved\n | valid|visited\n)(?![\\w-]|\\s*[;}])", + "match": "(?xi)\n(:)(:*)\n(?: active|any-link|checked|default|defined|disabled|empty|enabled|first\n | (?:first|last|only)-(?:child|of-type)|focus|focus-visible|focus-within\n | fullscreen|host|hover|in-range|indeterminate|invalid|left|link\n | optional|out-of-range|placeholder-shown|read-only|read-write\n | required|right|root|scope|target|unresolved\n | valid|visited\n)(?![\\w-]|\\s*[;}])", "name": "entity.other.attribute-name.pseudo-class.css" }, "pseudo-elements": { From a474fe9e2d954744e2a78dc8c1791ff187510346 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Mon, 25 Jun 2018 17:58:09 -0700 Subject: [PATCH 068/228] Fix bug disallowing modifying a config enjtry that didnt already exist (#52851) --- .../parts/extensions/electron-browser/extensionsActions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts index 005c98ddec0..66f6d8c2e03 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts @@ -2307,7 +2307,7 @@ export class AddToWorkspaceFolderRecommendationsAction extends AbstractConfigure return this.getWorkspaceFolderExtensionsConfigContent(configurationFile).then(content => { const extensionIdLowerCase = extensionId.toLowerCase(); if (shouldRecommend) { - if (content.recommendations.some(e => e.toLowerCase() === extensionIdLowerCase)) { + if ((content.recommendations || []).some(e => e.toLowerCase() === extensionIdLowerCase)) { this.notificationService.info(localize('AddToWorkspaceFolderRecommendations.alreadyExists', 'This extension is already present in this workspace folder\'s recommendations.')); return TPromise.as(null); } @@ -2319,7 +2319,7 @@ export class AddToWorkspaceFolderRecommendationsAction extends AbstractConfigure }); } else { - if (content.unwantedRecommendations.some(e => e.toLowerCase() === extensionIdLowerCase)) { + if ((content.unwantedRecommendations || []).some(e => e.toLowerCase() === extensionIdLowerCase)) { this.notificationService.info(localize('AddToWorkspaceFolderIgnoredRecommendations.alreadyExists', 'This extension is already present in this workspace folder\'s unwanted recommendations.')); return TPromise.as(null); } From 6a5918165d45ecbb5ddc9b3733810be89acb3781 Mon Sep 17 00:00:00 2001 From: Ramya Rao Date: Mon, 25 Jun 2018 21:49:06 -0700 Subject: [PATCH 069/228] Experiments (#52848) Infrastructure to run experiments --- .../common/extensionManagement.ts | 3 +- src/vs/platform/node/product.ts | 1 + .../platform/telemetry/common/experiments.ts | 92 --- src/vs/workbench/electron-browser/shell.ts | 8 - .../electron-browser/experimentalPrompt.ts | 92 +++ .../experiments.contribution.ts | 16 + .../experiments/node/experimentService.ts | 397 +++++++++++ .../experimentalPrompts.test.ts | 115 ++++ .../test/node/experimentService.test.ts | 650 ++++++++++++++++++ .../electron-browser/extensionTipsService.ts | 28 +- .../electron-browser/extensionsViews.ts | 24 +- .../extensionsTipsService.test.ts | 12 + .../parts/stats/node/workspaceStats.ts | 3 + .../quickopen.perf.integrationTest.ts | 12 - src/vs/workbench/workbench.main.ts | 1 + 15 files changed, 1338 insertions(+), 116 deletions(-) delete mode 100644 src/vs/platform/telemetry/common/experiments.ts create mode 100644 src/vs/workbench/parts/experiments/electron-browser/experimentalPrompt.ts create mode 100644 src/vs/workbench/parts/experiments/electron-browser/experiments.contribution.ts create mode 100644 src/vs/workbench/parts/experiments/node/experimentService.ts create mode 100644 src/vs/workbench/parts/experiments/test/electron-browser/experimentalPrompts.test.ts create mode 100644 src/vs/workbench/parts/experiments/test/node/experimentService.test.ts diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 3257a0ba2b4..16d69ec9f5b 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -419,7 +419,8 @@ export enum ExtensionRecommendationReason { Workspace, File, Executable, - DynamicWorkspace + DynamicWorkspace, + Experimental } export const ExtensionsLabel = localize('extensions', "Extensions"); diff --git a/src/vs/platform/node/product.ts b/src/vs/platform/node/product.ts index d97e871be34..b4ed6f55eab 100644 --- a/src/vs/platform/node/product.ts +++ b/src/vs/platform/node/product.ts @@ -22,6 +22,7 @@ export interface IProductConfiguration { commit?: string; settingsSearchBuildId?: number; settingsSearchUrl?: string; + experimentsUrl?: string; date: string; extensionsGallery: { serviceUrl: string; diff --git a/src/vs/platform/telemetry/common/experiments.ts b/src/vs/platform/telemetry/common/experiments.ts deleted file mode 100644 index 861a0a14621..00000000000 --- a/src/vs/platform/telemetry/common/experiments.ts +++ /dev/null @@ -1,92 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IStorageService } from 'vs/platform/storage/common/storage'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { deepClone } from 'vs/base/common/objects'; - -/* __GDPR__FRAGMENT__ - "IExperiments" : { - } -*/ -export interface IExperiments { -} - -export const IExperimentService = createDecorator('experimentService'); - -export interface IExperimentService { - - _serviceBrand: any; - - getExperiments(): IExperiments; -} - -export class ExperimentService implements IExperimentService { - - _serviceBrand: any; - - private experiments: IExperiments = {}; // Shortcut while there are no experiments. - - constructor( - @IStorageService private storageService: IStorageService, - @IConfigurationService private configurationService: IConfigurationService, - ) { } - - getExperiments() { - if (!this.experiments) { - this.experiments = loadExperiments(this.storageService, this.configurationService); - } - return this.experiments; - } -} - -function loadExperiments(storageService: IStorageService, configurationService: IConfigurationService): IExperiments { - const experiments = splitExperimentsRandomness(storageService); - return applyOverrides(experiments, configurationService); -} - -function applyOverrides(experiments: IExperiments, configurationService: IConfigurationService): IExperiments { - const experimentsConfig = getExperimentsOverrides(configurationService); - Object.keys(experiments).forEach(key => { - if (key in experimentsConfig) { - experiments[key] = experimentsConfig[key]; - } - }); - return experiments; -} - -function splitExperimentsRandomness(storageService: IStorageService): IExperiments { - const random1 = getExperimentsRandomness(storageService); - const [/* random2 */, /* ripgrepQuickSearch */] = splitRandom(random1); - // const [/* random3 */, /* deployToAzureQuickLink */] = splitRandom(random2); - // const [random4, /* mergeQuickLinks */] = splitRandom(random3); - // const [random5, /* enableWelcomePage */] = splitRandom(random4); - return { - // ripgrepQuickSearch, - }; -} - -function getExperimentsRandomness(storageService: IStorageService) { - const key = 'experiments.randomness'; - let valueString = storageService.get(key); - if (!valueString) { - valueString = Math.random().toString(); - storageService.store(key, valueString); - } - - return parseFloat(valueString); -} - -function splitRandom(random: number): [number, boolean] { - const scaled = random * 2; - const i = Math.floor(scaled); - return [scaled - i, i === 1]; -} - -function getExperimentsOverrides(configurationService: IConfigurationService): IExperiments { - return deepClone(configurationService.getValue('experiments')) || {}; -} diff --git a/src/vs/workbench/electron-browser/shell.ts b/src/vs/workbench/electron-browser/shell.ts index e4320501ecd..356a0e3a2c5 100644 --- a/src/vs/workbench/electron-browser/shell.ts +++ b/src/vs/workbench/electron-browser/shell.ts @@ -20,7 +20,6 @@ import { ContextViewService } from 'vs/platform/contextview/browser/contextViewS import { Workbench, IWorkbenchStartedInfo } from 'vs/workbench/electron-browser/workbench'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService, configurationTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; -import { IExperimentService, ExperimentService } from 'vs/platform/telemetry/common/experiments'; import { ITelemetryAppenderChannel, TelemetryAppenderClient } from 'vs/platform/telemetry/common/telemetryIpc'; import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService'; import ErrorTelemetry from 'vs/platform/telemetry/browser/errorTelemetry'; @@ -125,7 +124,6 @@ export class WorkbenchShell { private configurationService: IConfigurationService; private contextService: IWorkspaceContextService; private telemetryService: ITelemetryService; - private experimentService: IExperimentService; private extensionService: ExtensionService; private broadcastService: IBroadcastService; private timerService: ITimerService; @@ -254,7 +252,6 @@ export class WorkbenchShell { "customKeybindingsCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "theme": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, "language": { "classification": "SystemMetaData", "purpose": "BusinessInsight" }, - "experiments": { "${inline}": [ "${IExperiments}" ] }, "pinnedViewlets": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, "restoredViewlet": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, "restoredEditors": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, @@ -272,7 +269,6 @@ export class WorkbenchShell { customKeybindingsCount: info.customKeybindingsCount, theme: this.themeService.getColorTheme().id, language: platform.language, - experiments: this.experimentService.getExperiments(), pinnedViewlets: info.pinnedViewlets, restoredViewlet: info.restoredViewlet, restoredEditors: info.restoredEditorsCount, @@ -367,10 +363,6 @@ export class WorkbenchShell { // Hash serviceCollection.set(IHashService, new SyncDescriptor(HashService)); - // Experiments - this.experimentService = instantiationService.createInstance(ExperimentService); - serviceCollection.set(IExperimentService, this.experimentService); - // Telemetry if (this.environmentService.isBuilt && !this.environmentService.isExtensionDevelopment && !this.environmentService.args['disable-telemetry'] && !!product.enableTelemetry) { const channel = getDelayedChannel(sharedProcess.then(c => c.getChannel('telemetryAppender'))); diff --git a/src/vs/workbench/parts/experiments/electron-browser/experimentalPrompt.ts b/src/vs/workbench/parts/experiments/electron-browser/experimentalPrompt.ts new file mode 100644 index 00000000000..9c59b4b6784 --- /dev/null +++ b/src/vs/workbench/parts/experiments/electron-browser/experimentalPrompt.ts @@ -0,0 +1,92 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; +import { INotificationService, Severity, IPromptChoice } from 'vs/platform/notification/common/notification'; +import { IExperimentService, IExperiment, ExperimentActionType, IExperimentActionPromptProperties, ExperimentState } from 'vs/workbench/parts/experiments/node/experimentService'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IExtensionsViewlet } from 'vs/workbench/parts/extensions/common/extensions'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; + +export class ExperimentalPrompts extends Disposable implements IWorkbenchContribution { + private _disposables: IDisposable[] = []; + + constructor( + @IExperimentService private experimentService: IExperimentService, + @IViewletService private viewletService: IViewletService, + @INotificationService private notificationService: INotificationService, + @ITelemetryService private telemetryService: ITelemetryService + + ) { + super(); + this.experimentService.onExperimentEnabled(e => { + if (e.action && e.action.type === ExperimentActionType.Prompt && e.state === ExperimentState.Run) { + this.showExperimentalPrompts(e); + } + }, this, this._disposables); + } + + private showExperimentalPrompts(experiment: IExperiment): void { + if (!experiment || !experiment.enabled || !experiment.action || experiment.state !== ExperimentState.Run) { + return; + } + + const logTelemetry = (commandText?: string) => { + /* __GDPR__ + "experimentalPrompts" : { + "experimentId": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "commandText": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "cancelled": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + } + */ + this.telemetryService.publicLog('experimentalPrompts', { + experimentId: experiment.id, + commandText, + cancelled: !commandText + }); + }; + + const actionProperties = (experiment.action.properties); + if (!actionProperties || !actionProperties.promptText) { + return; + } + if (!actionProperties.commands) { + actionProperties.commands = []; + } + + const choices: IPromptChoice[] = actionProperties.commands.map(command => { + return { + label: command.text, + run: () => { + logTelemetry(command.text); + if (command.externalLink) { + window.open(command.externalLink); + return; + } + if (command.curatedExtensionsKey && Array.isArray(command.curatedExtensionsList)) { + this.viewletService.openViewlet('workbench.view.extensions', true) + .then(viewlet => viewlet as IExtensionsViewlet) + .then(viewlet => { + if (viewlet) { + viewlet.search('curated:' + command.curatedExtensionsKey); + } + }); + return; + } + + this.experimentService.markAsCompleted(experiment.id); + + } + }; + }); + + this.notificationService.prompt(Severity.Info, actionProperties.promptText, choices, logTelemetry); + } + + dispose() { + this._disposables = dispose(this._disposables); + } +} diff --git a/src/vs/workbench/parts/experiments/electron-browser/experiments.contribution.ts b/src/vs/workbench/parts/experiments/electron-browser/experiments.contribution.ts new file mode 100644 index 00000000000..93811199b24 --- /dev/null +++ b/src/vs/workbench/parts/experiments/electron-browser/experiments.contribution.ts @@ -0,0 +1,16 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + + +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IExperimentService, ExperimentService } from 'vs/workbench/parts/experiments/node/experimentService'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { ExperimentalPrompts } from 'vs/workbench/parts/experiments/electron-browser/experimentalPrompt'; + +registerSingleton(IExperimentService, ExperimentService); + +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(ExperimentalPrompts, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/parts/experiments/node/experimentService.ts b/src/vs/workbench/parts/experiments/node/experimentService.ts new file mode 100644 index 00000000000..66d54a3904b --- /dev/null +++ b/src/vs/workbench/parts/experiments/node/experimentService.ts @@ -0,0 +1,397 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import product from 'vs/platform/node/product'; + +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; + +import { IExtensionManagementService, LocalExtensionType } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IRequestService } from 'vs/platform/request/node/request'; + +import { TPromise } from 'vs/base/common/winjs.base'; +import { language } from 'vs/base/common/platform'; +import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { match } from 'vs/base/common/glob'; +import { asJson } from 'vs/base/node/request'; + +import { ITextFileService, StateChange } from 'vs/workbench/services/textfile/common/textfiles'; +import { WorkspaceStats } from 'vs/workbench/parts/stats/node/workspaceStats'; +import { Emitter, Event } from 'vs/base/common/event'; + + +interface IExperimentStorageState { + enabled: boolean; + state: ExperimentState; + editCount?: number; + lastEditedDate?: string; +} + +export enum ExperimentState { + Evaluating, + NoRun, + Run, + Complete +} + +interface IRawExperiment { + id: string; + enabled?: boolean; + condition?: { + insidersOnly?: boolean; + displayLanguage?: string; + installedExtensions?: { + excludes?: string[]; + includes?: string[]; + }, + fileEdits?: { + filePathPattern?: string; + workspaceIncludes?: string[]; + workspaceExcludes?: string[]; + minEditCount: number; + }, + userProbability?: number; + }; + action?: { type: string; properties: any }; +} + +export interface IExperimentActionPromptProperties { + promptText: string; + commands: IExperimentActionPromptCommand[]; +} + +interface IExperimentActionPromptCommand { + text: string; + externalLink?: string; + dontShowAgain?: boolean; + curatedExtensionsKey?: string; + curatedExtensionsList?: string[]; +} + +export interface IExperiment { + id: string; + enabled: boolean; + state: ExperimentState; + action?: IExperimentAction; +} + +export enum ExperimentActionType { + Custom, + Prompt, + AddToRecommendations +} + +interface IExperimentAction { + type: ExperimentActionType; + properties: any; +} + +export interface IExperimentService { + _serviceBrand: any; + getExperimentById(id: string): TPromise; + getExperimentsToRunByType(type: ExperimentActionType): TPromise; + getCuratedExtensionsList(curatedExtensionsKey: string): TPromise; + markAsCompleted(experimentId: string): void; + + onExperimentEnabled: Event; +} + +export const IExperimentService = createDecorator('experimentService'); + +export class ExperimentService extends Disposable implements IExperimentService { + _serviceBrand: any; + private _experiments: IExperiment[] = []; + private _loadExperimentsPromise: TPromise; + private _curatedMapping = Object.create(null); + private _disposables: IDisposable[] = []; + + private readonly _onExperimentEnabled: Emitter = new Emitter(); + + onExperimentEnabled: Event = this._onExperimentEnabled.event; + constructor( + @IStorageService private storageService: IStorageService, + @IExtensionManagementService private extensionManagementService: IExtensionManagementService, + @ITextFileService private textFileService: ITextFileService, + @IEnvironmentService private environmentService: IEnvironmentService, + @ITelemetryService private telemetryService: ITelemetryService, + @ILifecycleService private lifecycleService: ILifecycleService, + @IRequestService private requestService: IRequestService + ) { + super(); + + this._loadExperimentsPromise = TPromise.wrap(this.lifecycleService.when(LifecyclePhase.Eventually)).then(() => this.loadExperiments()); + } + + public getExperimentById(id: string): TPromise { + return this._loadExperimentsPromise.then(() => { + return this._experiments.filter(x => x.id === id)[0]; + }); + } + + public getExperimentsToRunByType(type: ExperimentActionType): TPromise { + return this._loadExperimentsPromise.then(() => { + if (type === ExperimentActionType.Custom) { + return this._experiments.filter(x => x.enabled && x.state === ExperimentState.Run && (!x.action || x.action.type === type)); + } + return this._experiments.filter(x => x.enabled && x.state === ExperimentState.Run && x.action && x.action.type === type); + }); + } + + public getCuratedExtensionsList(curatedExtensionsKey: string): TPromise { + return this._loadExperimentsPromise.then(() => { + for (let i = 0; i < this._experiments.length; i++) { + if (this._experiments[i].enabled + && this._experiments[i].state === ExperimentState.Run + && this._curatedMapping[this._experiments[i].id] + && this._curatedMapping[this._experiments[i].id].curatedExtensionsKey === curatedExtensionsKey) { + return this._curatedMapping[this._experiments[i].id].curatedExtensionsList; + } + } + return []; + }); + } + + public markAsCompleted(experimentId: string): void { + const storageKey = 'experiments.' + experimentId; + const experimentState: IExperimentStorageState = safeParse(this.storageService.get(storageKey, StorageScope.GLOBAL), {}); + experimentState.state = ExperimentState.Complete; + this.storageService.store(storageKey, JSON.stringify(experimentState), StorageScope.GLOBAL); + } + + protected getExperiments(): TPromise { + if (!product.experimentsUrl) { + return TPromise.as([]); + } + return this.requestService.request({ type: 'GET', url: product.experimentsUrl }).then(context => { + if (context.res.statusCode !== 200) { + return TPromise.as(null); + } + return asJson(context).then(result => { + return Array.isArray(result['experiments']) ? result['experiments'] : []; + }); + }, () => TPromise.as(null)); + } + + private loadExperiments(): TPromise { + return this.getExperiments().then(rawExperiments => { + // Offline mode + if (!rawExperiments) { + const allExperimentIdsFromStorage = safeParse(this.storageService.get('allExperiments', StorageScope.GLOBAL), []); + if (Array.isArray(allExperimentIdsFromStorage)) { + allExperimentIdsFromStorage.forEach(experimentId => { + const storageKey = 'experiments.' + experimentId; + const experimentState: IExperimentStorageState = safeParse(this.storageService.get(storageKey, StorageScope.GLOBAL), null); + if (experimentState) { + this._experiments.push({ + id: experimentId, + enabled: experimentState.enabled, + state: experimentState.state + }); + } + }); + } + return TPromise.as(null); + } + + // Clear disbaled/deleted experiments from storage + const allExperimentIdsFromStorage = safeParse(this.storageService.get('allExperiments', StorageScope.GLOBAL), []); + const enabledExperiments = rawExperiments.filter(experiment => !!experiment.enabled).map(experiment => experiment.id.toLowerCase()); + if (Array.isArray(allExperimentIdsFromStorage)) { + allExperimentIdsFromStorage.forEach(experiment => { + if (enabledExperiments.indexOf(experiment) === -1) { + this.storageService.remove('experiments.' + experiment); + } + }); + } + this.storageService.store('allExperiments', JSON.stringify(enabledExperiments), StorageScope.GLOBAL); + + const promises = rawExperiments.map(experiment => { + const processedExperiment: IExperiment = { + id: experiment.id, + enabled: !!experiment.enabled, + state: !!experiment.enabled ? ExperimentState.Evaluating : ExperimentState.NoRun + }; + + if (experiment.action) { + processedExperiment.action = { + type: ExperimentActionType[experiment.action.type] || ExperimentActionType.Custom, + properties: experiment.action.properties + }; + if (processedExperiment.action.type === ExperimentActionType.Prompt) { + ((processedExperiment.action.properties).commands || []).forEach(x => { + if (x.curatedExtensionsKey && Array.isArray(x.curatedExtensionsList)) { + this._curatedMapping[experiment.id] = x; + } + }); + } + } + this._experiments.push(processedExperiment); + + if (!processedExperiment.enabled) { + return TPromise.as(null); + } + + const storageKey = 'experiments.' + experiment.id; + const experimentState: IExperimentStorageState = safeParse(this.storageService.get(storageKey, StorageScope.GLOBAL), {}); + if (!experimentState.hasOwnProperty('enabled')) { + experimentState.enabled = processedExperiment.enabled; + } + if (!experimentState.hasOwnProperty('state')) { + experimentState.state = processedExperiment.enabled ? ExperimentState.Evaluating : ExperimentState.NoRun; + } else { + processedExperiment.state = experimentState.state; + } + + return this.shouldRunExperiment(experiment, processedExperiment).then((state: ExperimentState) => { + experimentState.state = processedExperiment.state = state; + this.storageService.store(storageKey, JSON.stringify(experimentState)); + + if (state === ExperimentState.Run) { + this._onExperimentEnabled.fire(processedExperiment); + } + return TPromise.as(null); + }); + + }); + return TPromise.join(promises).then(() => { + this.telemetryService.publicLog('experiments', this._experiments); + }); + }); + } + + private shouldRunExperiment(experiment: IRawExperiment, processedExperiment: IExperiment): TPromise { + if (processedExperiment.state !== ExperimentState.Evaluating) { + return TPromise.wrap(processedExperiment.state); + } + + if (!experiment.enabled) { + return TPromise.wrap(ExperimentState.NoRun); + } + + if (!experiment.condition) { + return TPromise.wrap(ExperimentState.Run); + } + + if (this.environmentService.appQuality === 'stable' && experiment.condition.insidersOnly === true) { + return TPromise.wrap(ExperimentState.NoRun); + } + + if (typeof experiment.condition.displayLanguage === 'string') { + let localeToCheck = experiment.condition.displayLanguage.toLowerCase(); + let displayLanguage = language.toLowerCase(); + + if (localeToCheck !== displayLanguage) { + const a = displayLanguage.indexOf('-'); + const b = localeToCheck.indexOf('-'); + if (a > -1) { + displayLanguage = displayLanguage.substr(0, a); + } + if (b > -1) { + localeToCheck = localeToCheck.substr(0, b); + } + if (displayLanguage !== localeToCheck) { + return TPromise.wrap(ExperimentState.NoRun); + } + } + } + + if (!experiment.condition.userProbability) { + experiment.condition.userProbability = 1; + } + + let extensionsCheckPromise = TPromise.as(true); + if (experiment.condition.installedExtensions) { + extensionsCheckPromise = this.extensionManagementService.getInstalled(LocalExtensionType.User).then(locals => { + let includesCheck = true; + let excludesCheck = true; + const localExtensions = locals.map(local => `${local.manifest.publisher.toLowerCase()}.${local.manifest.name.toLowerCase()}`); + if (Array.isArray(experiment.condition.installedExtensions.includes) && experiment.condition.installedExtensions.includes.length) { + const extensionIncludes = experiment.condition.installedExtensions.includes.map(e => e.toLowerCase()); + includesCheck = localExtensions.some(e => extensionIncludes.indexOf(e) > -1); + } + if (Array.isArray(experiment.condition.installedExtensions.excludes) && experiment.condition.installedExtensions.excludes.length) { + const extensionExcludes = experiment.condition.installedExtensions.excludes.map(e => e.toLowerCase()); + excludesCheck = !localExtensions.some(e => extensionExcludes.indexOf(e) > -1); + } + return includesCheck && excludesCheck; + }); + } + + const storageKey = 'experiments.' + experiment.id; + const experimentState: IExperimentStorageState = safeParse(this.storageService.get(storageKey, StorageScope.GLOBAL), {}); + + return extensionsCheckPromise.then(success => { + if (!success || !experiment.condition.fileEdits || typeof experiment.condition.fileEdits.minEditCount !== 'number') { + const runExperiment = success && Math.random() < experiment.condition.userProbability; + return runExperiment ? ExperimentState.Run : ExperimentState.NoRun; + } + + experimentState.editCount = experimentState.editCount || 0; + if (experimentState.editCount >= experiment.condition.fileEdits.minEditCount) { + return ExperimentState.Run; + } + + const onSaveHandler = this.textFileService.models.onModelsSaved(e => { + const date = new Date().toDateString(); + const latestExperimentState: IExperimentStorageState = safeParse(this.storageService.get(storageKey, StorageScope.GLOBAL), {}); + if (latestExperimentState.state !== ExperimentState.Evaluating) { + onSaveHandler.dispose(); + return; + } + e.forEach(event => { + if (event.kind !== StateChange.SAVED + || latestExperimentState.state !== ExperimentState.Evaluating + || date === latestExperimentState.lastEditedDate + || latestExperimentState.editCount >= experiment.condition.fileEdits.minEditCount) { + return; + } + let filePathCheck = true; + let workspaceCheck = true; + + if (typeof experiment.condition.fileEdits.filePathPattern === 'string') { + filePathCheck = match(experiment.condition.fileEdits.filePathPattern, event.resource.fsPath); + } + if (Array.isArray(experiment.condition.fileEdits.workspaceIncludes) && experiment.condition.fileEdits.workspaceIncludes.length) { + workspaceCheck = experiment.condition.fileEdits.workspaceIncludes.some(x => !!WorkspaceStats.tags[x]); + } + if (workspaceCheck && Array.isArray(experiment.condition.fileEdits.workspaceExcludes) && experiment.condition.fileEdits.workspaceExcludes.length) { + workspaceCheck = !experiment.condition.fileEdits.workspaceExcludes.some(x => !!WorkspaceStats.tags[x]); + } + if (filePathCheck && workspaceCheck) { + latestExperimentState.editCount = (latestExperimentState.editCount || 0) + 1; + latestExperimentState.lastEditedDate = date; + this.storageService.store(storageKey, JSON.stringify(latestExperimentState), StorageScope.GLOBAL); + } + }); + if (latestExperimentState.editCount >= experiment.condition.fileEdits.minEditCount) { + processedExperiment.state = latestExperimentState.state = Math.random() < experiment.condition.userProbability ? ExperimentState.Run : ExperimentState.NoRun; + this.storageService.store(storageKey, JSON.stringify(latestExperimentState), StorageScope.GLOBAL); + if (latestExperimentState.state === ExperimentState.Run && ExperimentActionType[experiment.action.type] === ExperimentActionType.Prompt) { + this._onExperimentEnabled.fire(processedExperiment); + } + } + }); + this._disposables.push(onSaveHandler); + return ExperimentState.Evaluating; + }); + } + + dispose() { + this._disposables = dispose(this._disposables); + } +} + + +function safeParse(text: string, defaultObject: any) { + try { + return JSON.parse(text) || defaultObject; + } + catch (e) { + return defaultObject; + } +} \ No newline at end of file diff --git a/src/vs/workbench/parts/experiments/test/electron-browser/experimentalPrompts.test.ts b/src/vs/workbench/parts/experiments/test/electron-browser/experimentalPrompts.test.ts new file mode 100644 index 00000000000..4355a272c0d --- /dev/null +++ b/src/vs/workbench/parts/experiments/test/electron-browser/experimentalPrompts.test.ts @@ -0,0 +1,115 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import * as assert from 'assert'; + +import { IExperiment, ExperimentActionType, IExperimentService, ExperimentState } from 'vs/workbench/parts/experiments/node/experimentService'; + +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { Emitter } from 'vs/base/common/event'; +import { TestExperimentService } from 'vs/workbench/parts/experiments/test/node/experimentService.test'; +import { ExperimentalPrompts } from 'vs/workbench/parts/experiments/electron-browser/experimentalPrompt'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { INotificationService, Severity, IPromptChoice } from 'vs/platform/notification/common/notification'; +import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; +import { TestLifecycleService } from 'vs/workbench/test/workbenchTestServices'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; +import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; +import { TPromise } from 'vs/base/common/winjs.base'; + + +suite('Experimental Prompts', () => { + let instantiationService: TestInstantiationService; + let experimentService: TestExperimentService; + let experimentalPrompt: ExperimentalPrompts; + let onExperimentEnabledEvent: Emitter; + + let storageData = {}; + const promptText = 'Hello there! Can you see this?'; + const experiment: IExperiment = + { + id: 'experiment1', + enabled: true, + state: ExperimentState.Run, + action: { + type: ExperimentActionType.Prompt, + properties: { + promptText, + commands: [ + { + text: 'Yes', + dontShowAgain: true + }, + { + text: 'No' + } + ] + } + } + }; + + suiteSetup(() => { + instantiationService = new TestInstantiationService(); + + instantiationService.stub(ILifecycleService, new TestLifecycleService()); + instantiationService.stub(ITelemetryService, NullTelemetryService); + + onExperimentEnabledEvent = new Emitter(); + + }); + + setup(() => { + storageData = {}; + instantiationService.stub(IStorageService, { + get: (a, b, c) => a === 'experiments.experiment1' ? JSON.stringify(storageData) : c, + store: (a, b, c) => { + if (a === 'experiments.experiment1') { + storageData = JSON.parse(b); + } + } + }); + instantiationService.stub(INotificationService, new TestNotificationService()); + experimentService = instantiationService.createInstance(TestExperimentService); + experimentService.onExperimentEnabled = onExperimentEnabledEvent.event; + instantiationService.stub(IExperimentService, experimentService); + }); + + teardown(() => { + if (experimentService) { + experimentService.dispose(); + } + if (experimentalPrompt) { + experimentalPrompt.dispose(); + } + }); + + + test('Show experimental prompt if experiment should be run. Choosing an option should mark experiment as complete', () => { + + storageData = { + enabled: true, + state: ExperimentState.Run + }; + + instantiationService.stub(INotificationService, { + prompt: (a: Severity, b: string, c: IPromptChoice[], d) => { + assert.equal(b, promptText); + assert.equal(c.length, 2); + c[0].run(); + } + }); + + experimentalPrompt = instantiationService.createInstance(ExperimentalPrompts); + onExperimentEnabledEvent.fire(experiment); + + return TPromise.as(null).then(result => { + assert.equal(storageData['state'], ExperimentState.Complete); + }); + + }); +}); \ No newline at end of file diff --git a/src/vs/workbench/parts/experiments/test/node/experimentService.test.ts b/src/vs/workbench/parts/experiments/test/node/experimentService.test.ts new file mode 100644 index 00000000000..fe37e429d49 --- /dev/null +++ b/src/vs/workbench/parts/experiments/test/node/experimentService.test.ts @@ -0,0 +1,650 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import * as assert from 'assert'; +import { ExperimentService, ExperimentActionType, ExperimentState } from 'vs/workbench/parts/experiments/node/experimentService'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { TestLifecycleService } from 'vs/workbench/test/workbenchTestServices'; +import { + IExtensionManagementService, DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IExtensionIdentifier, + IExtensionEnablementService, ILocalExtension, LocalExtensionType +} from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ExtensionManagementService, getLocalExtensionIdFromManifest } from 'vs/platform/extensionManagement/node/extensionManagementService'; +import { Emitter } from 'vs/base/common/event'; +import { TestExtensionEnablementService } from 'vs/platform/extensionManagement/test/common/extensionEnablementService.test'; +import { URLService } from 'vs/platform/url/common/urlService'; +import { IURLService } from 'vs/platform/url/common/url'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { assign } from 'vs/base/common/objects'; +import URI from 'vs/base/common/uri'; + +let experimentData = { + experiments: [] +}; + +const local = aLocalExtension('installedExtension1', { version: '1.0.0' }); + +function aLocalExtension(name: string = 'someext', manifest: any = {}, properties: any = {}): ILocalExtension { + const localExtension = Object.create({ manifest: {} }); + assign(localExtension, { type: LocalExtensionType.User, manifest: {}, location: URI.file(`pub.${name}`) }, properties); + assign(localExtension.manifest, { name, publisher: 'pub', version: '1.0.0' }, manifest); + localExtension.identifier = { id: getLocalExtensionIdFromManifest(localExtension.manifest) }; + localExtension.metadata = { id: localExtension.identifier.id, publisherId: localExtension.manifest.publisher, publisherDisplayName: 'somename' }; + return localExtension; +} + +export class TestExperimentService extends ExperimentService { + public getExperiments(): TPromise { + return TPromise.wrap(experimentData.experiments); + } +} + +suite('Experiment Service', () => { + let instantiationService: TestInstantiationService; + let testConfigurationService: TestConfigurationService; + let testObject: ExperimentService; + let installEvent: Emitter, + didInstallEvent: Emitter, + uninstallEvent: Emitter, + didUninstallEvent: Emitter; + + suiteSetup(() => { + instantiationService = new TestInstantiationService(); + installEvent = new Emitter(); + didInstallEvent = new Emitter(); + uninstallEvent = new Emitter(); + didUninstallEvent = new Emitter(); + + instantiationService.stub(IExtensionManagementService, ExtensionManagementService); + instantiationService.stub(IExtensionManagementService, 'onInstallExtension', installEvent.event); + instantiationService.stub(IExtensionManagementService, 'onDidInstallExtension', didInstallEvent.event); + instantiationService.stub(IExtensionManagementService, 'onUninstallExtension', uninstallEvent.event); + instantiationService.stub(IExtensionManagementService, 'onDidUninstallExtension', didUninstallEvent.event); + instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + instantiationService.stub(ITelemetryService, NullTelemetryService); + instantiationService.stub(IURLService, URLService); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + testConfigurationService = new TestConfigurationService(); + instantiationService.stub(IConfigurationService, testConfigurationService); + instantiationService.stub(ILifecycleService, new TestLifecycleService()); + instantiationService.stub(IStorageService, { get: (a, b, c) => c, getBoolean: (a, b, c) => c, store: () => { } }); + + setup(() => { + instantiationService.stub(IEnvironmentService, {}); + instantiationService.stub(IStorageService, { get: (a, b, c) => c, getBoolean: (a, b, c) => c, store: () => { } }); + }); + + teardown(() => { + if (testObject) { + testObject.dispose(); + } + }); + }); + + test('Simple Experiment Test', () => { + experimentData = { + experiments: [ + { + id: 'experiment1' + }, + { + id: 'experiment2', + enabled: false + }, + { + id: 'experiment3', + enabled: true + }, + { + id: 'experiment4', + enabled: true, + condition: { + + } + }, + { + id: 'experiment5', + enabled: true, + condition: { + insidersOnly: true + } + } + ] + }; + + testObject = instantiationService.createInstance(TestExperimentService); + const tests = []; + tests.push(testObject.getExperimentById('experiment1')); + tests.push(testObject.getExperimentById('experiment2')); + tests.push(testObject.getExperimentById('experiment3')); + tests.push(testObject.getExperimentById('experiment4')); + tests.push(testObject.getExperimentById('experiment5')); + + return TPromise.join(tests).then(results => { + assert.equal(results[0].id, 'experiment1'); + assert.equal(results[0].enabled, false); + assert.equal(results[0].state, ExperimentState.NoRun); + + assert.equal(results[1].id, 'experiment2'); + assert.equal(results[1].enabled, false); + assert.equal(results[1].state, ExperimentState.NoRun); + + assert.equal(results[2].id, 'experiment3'); + assert.equal(results[2].enabled, true); + assert.equal(results[2].state, ExperimentState.Run); + + assert.equal(results[3].id, 'experiment4'); + assert.equal(results[3].enabled, true); + assert.equal(results[3].state, ExperimentState.Run); + + assert.equal(results[4].id, 'experiment5'); + assert.equal(results[4].enabled, true); + assert.equal(results[4].state, ExperimentState.Run); + }); + }); + + test('Insiders only experiment shouldnt be enabled in stable', () => { + experimentData = { + experiments: [ + { + id: 'experiment1', + enabled: true, + condition: { + insidersOnly: true + } + } + ] + }; + + instantiationService.stub(IEnvironmentService, { appQuality: 'stable' }); + testObject = instantiationService.createInstance(TestExperimentService); + return testObject.getExperimentById('experiment1').then(result => { + assert.equal(result.enabled, true); + assert.equal(result.state, ExperimentState.NoRun); + }); + }); + + test('Experiment with no matching display language should be disabled', () => { + experimentData = { + experiments: [ + { + id: 'experiment1', + enabled: true, + condition: { + displayLanguage: 'somethingthat-nooneknows' + } + } + ] + }; + + testObject = instantiationService.createInstance(TestExperimentService); + return testObject.getExperimentById('experiment1').then(result => { + assert.equal(result.enabled, true); + assert.equal(result.state, ExperimentState.NoRun); + }); + }); + + test('Experiment with condition type InstalledExtensions is enabled when one of the expected extensions is installed', () => { + experimentData = { + experiments: [ + { + id: 'experiment1', + enabled: true, + condition: { + installedExtensions: { + inlcudes: ['pub.installedExtension1', 'uninstalled-extention-id'] + } + } + } + ] + }; + + testObject = instantiationService.createInstance(TestExperimentService); + return testObject.getExperimentById('experiment1').then(result => { + assert.equal(result.enabled, true); + assert.equal(result.state, ExperimentState.Run); + }); + }); + + test('Experiment with condition type InstalledExtensions is disabled when none of the expected extensions is installed', () => { + experimentData = { + experiments: [ + { + id: 'experiment1', + enabled: true, + condition: { + installedExtensions: { + includes: ['uninstalled-extention-id1', 'uninstalled-extention-id2'] + } + } + } + ] + }; + + testObject = instantiationService.createInstance(TestExperimentService); + return testObject.getExperimentById('experiment1').then(result => { + assert.equal(result.enabled, true); + assert.equal(result.state, ExperimentState.NoRun); + }); + }); + + test('Experiment with condition type InstalledExtensions is disabled when one of the exlcuded extensions is installed', () => { + experimentData = { + experiments: [ + { + id: 'experiment1', + enabled: true, + condition: { + installedExtensions: { + excludes: ['pub.installedExtension1', 'uninstalled-extention-id2'] + } + } + } + ] + }; + + testObject = instantiationService.createInstance(TestExperimentService); + return testObject.getExperimentById('experiment1').then(result => { + assert.equal(result.enabled, true); + assert.equal(result.state, ExperimentState.NoRun); + }); + }); + + test('Experiment that is marked as complete should be disabled regardless of the conditions', () => { + experimentData = { + experiments: [ + { + id: 'experiment1', + enabled: true, + condition: { + installedExtensions: { + includes: ['pub.installedExtension1', 'uninstalled-extention-id2'] + } + } + } + ] + }; + + instantiationService.stub(IStorageService, { + get: (a, b, c) => a === 'experiments.experiment1' ? JSON.stringify({ state: ExperimentState.Complete }) : c, + store: (a, b, c) => { } + }); + + testObject = instantiationService.createInstance(TestExperimentService); + return testObject.getExperimentById('experiment1').then(result => { + assert.equal(result.enabled, true); + assert.equal(result.state, ExperimentState.Complete); + }); + }); + + test('Experiment with evaluate only once should read enablement from storage service', () => { + experimentData = { + experiments: [ + { + id: 'experiment1', + enabled: true, + condition: { + installedExtensions: { + excludes: ['pub.installedExtension1', 'uninstalled-extention-id2'] + }, + evaluateOnlyOnce: true + } + } + ] + }; + + instantiationService.stub(IStorageService, { + get: (a, b, c) => a === 'experiments.experiment1' ? JSON.stringify({ enabled: true, state: ExperimentState.Run }) : c, + store: (a, b, c) => { } + }); + testObject = instantiationService.createInstance(TestExperimentService); + return testObject.getExperimentById('experiment1').then(result => { + assert.equal(result.enabled, true); + assert.equal(result.state, ExperimentState.Run); + }); + }); + + test('Curated list should be available if experiment is enabled.', () => { + const promptText = 'Hello there! Can you see this?'; + const curatedExtensionsKey = 'AzureDeploy'; + const curatedExtensionsList = ['uninstalled-extention-id1', 'uninstalled-extention-id2']; + experimentData = { + experiments: [ + { + id: 'experiment1', + enabled: true, + action: { + type: 'Prompt', + properties: { + promptText, + commands: [ + { + text: 'Search Marketplace', + dontShowAgain: true, + curatedExtensionsKey, + curatedExtensionsList + }, + { + text: 'No' + } + ] + } + } + } + ] + }; + + testObject = instantiationService.createInstance(TestExperimentService); + return testObject.getExperimentById('experiment1').then(result => { + assert.equal(result.enabled, true); + assert.equal(result.state, ExperimentState.Run); + return testObject.getCuratedExtensionsList(curatedExtensionsKey).then(curatedList => { + assert.equal(curatedList, curatedExtensionsList); + }); + }); + }); + + test('Curated list shouldnt be available if experiment is disabled.', () => { + const promptText = 'Hello there! Can you see this?'; + const curatedExtensionsKey = 'AzureDeploy'; + const curatedExtensionsList = ['uninstalled-extention-id1', 'uninstalled-extention-id2']; + experimentData = { + experiments: [ + { + id: 'experiment1', + enabled: false, + action: { + type: 'Prompt', + properties: { + promptText, + commands: [ + { + text: 'Search Marketplace', + dontShowAgain: true, + curatedExtensionsKey, + curatedExtensionsList + }, + { + text: 'No' + } + ] + } + } + } + ] + }; + + testObject = instantiationService.createInstance(TestExperimentService); + return testObject.getExperimentById('experiment1').then(result => { + assert.equal(result.enabled, false); + assert.equal(result.state, ExperimentState.NoRun); + return testObject.getCuratedExtensionsList(curatedExtensionsKey).then(curatedList => { + assert.equal(curatedList.length, 0); + }); + }); + }); + + test('Experiment that is disabled or deleted should be removed from storage', () => { + experimentData = { + experiments: [ + { + id: 'experiment1', + enabled: false + }, + { + id: 'experiment3', + enabled: true + } + ] + }; + + let storageDataExperiment1 = { enabled: false }; + let storageDataExperiment2 = { enabled: false }; + let storageDataAllExperiments = ['experiment1', 'experiment2', 'experiment3']; + instantiationService.stub(IStorageService, { + get: (a, b, c) => { + switch (a) { + case 'experiments.experiment1': + return JSON.stringify(storageDataExperiment1); + case 'experiments.experiment2': + return JSON.stringify(storageDataExperiment2); + case 'allExperiments': + return JSON.stringify(storageDataAllExperiments); + default: + break; + } + return c; + }, + store: (a, b, c) => { + switch (a) { + case 'experiments.experiment1': + storageDataExperiment1 = JSON.parse(b); + break; + case 'experiments.experiment2': + storageDataExperiment2 = JSON.parse(b); + break; + case 'allExperiments': + storageDataAllExperiments = JSON.parse(b); + break; + default: + break; + } + }, + remove: a => { + switch (a) { + case 'experiments.experiment1': + storageDataExperiment1 = null; + break; + case 'experiments.experiment2': + storageDataExperiment2 = null; + break; + case 'allExperiments': + storageDataAllExperiments = null; + break; + default: + break; + } + } + }); + + testObject = instantiationService.createInstance(TestExperimentService); + const disabledExperiment = testObject.getExperimentById('experiment1').then(result => { + assert.equal(result.enabled, false); + assert.equal(!!storageDataExperiment1, false); + }); + const deletedExperiment = testObject.getExperimentById('experiment2').then(result => { + assert.equal(!!result, false); + assert.equal(!!storageDataExperiment2, false); + }); + return TPromise.join([disabledExperiment, deletedExperiment]).then(() => { + assert.equal(storageDataAllExperiments.length, 1); + assert.equal(storageDataAllExperiments[0], 'experiment3'); + }); + + }); + + test('Offline mode', () => { + experimentData = { + experiments: null + }; + + let storageDataExperiment1 = { enabled: true, state: ExperimentState.Run }; + let storageDataExperiment2 = { enabled: true, state: ExperimentState.NoRun }; + let storageDataExperiment3 = { enabled: true, state: ExperimentState.Evaluating }; + let storageDataExperiment4 = { enabled: true, state: ExperimentState.Complete }; + let storageDataAllExperiments = ['experiment1', 'experiment2', 'experiment3', 'experiment4']; + instantiationService.stub(IStorageService, { + get: (a, b, c) => { + switch (a) { + case 'experiments.experiment1': + return JSON.stringify(storageDataExperiment1); + case 'experiments.experiment2': + return JSON.stringify(storageDataExperiment2); + case 'experiments.experiment3': + return JSON.stringify(storageDataExperiment3); + case 'experiments.experiment4': + return JSON.stringify(storageDataExperiment4); + case 'allExperiments': + return JSON.stringify(storageDataAllExperiments); + default: + break; + } + return c; + }, + store: (a, b, c) => { + switch (a) { + case 'experiments.experiment1': + storageDataExperiment1 = JSON.parse(b); + break; + case 'experiments.experiment2': + storageDataExperiment2 = JSON.parse(b); + break; + case 'experiments.experiment3': + storageDataExperiment3 = JSON.parse(b); + break; + case 'experiments.experiment4': + storageDataExperiment4 = JSON.parse(b); + break; + case 'allExperiments': + storageDataAllExperiments = JSON.parse(b); + break; + default: + break; + } + }, + remove: a => { + switch (a) { + case 'experiments.experiment1': + storageDataExperiment1 = null; + break; + case 'experiments.experiment2': + storageDataExperiment2 = null; + break; + case 'experiments.experiment3': + storageDataExperiment3 = null; + break; + case 'experiments.experiment4': + storageDataExperiment4 = null; + break; + case 'allExperiments': + storageDataAllExperiments = null; + break; + default: + break; + } + } + }); + + testObject = instantiationService.createInstance(TestExperimentService); + + const tests = []; + tests.push(testObject.getExperimentById('experiment1')); + tests.push(testObject.getExperimentById('experiment2')); + tests.push(testObject.getExperimentById('experiment3')); + tests.push(testObject.getExperimentById('experiment4')); + + return TPromise.join(tests).then(results => { + assert.equal(results[0].id, 'experiment1'); + assert.equal(results[0].enabled, true); + assert.equal(results[0].state, ExperimentState.Run); + + assert.equal(results[1].id, 'experiment2'); + assert.equal(results[1].enabled, true); + assert.equal(results[1].state, ExperimentState.NoRun); + + assert.equal(results[2].id, 'experiment3'); + assert.equal(results[2].enabled, true); + assert.equal(results[2].state, ExperimentState.Evaluating); + + assert.equal(results[3].id, 'experiment4'); + assert.equal(results[3].enabled, true); + assert.equal(results[3].state, ExperimentState.Complete); + }); + + }); + + test('getExperimentByType', () => { + const customProperties = { + some: 'random-value' + }; + experimentData = { + experiments: [ + { + id: 'simple-experiment', + enabled: true + }, + { + id: 'custom-experiment', + enabled: true, + action: { + type: 'Custom', + properties: customProperties + } + }, + { + id: 'prompt-with-no-commands', + enabled: true, + action: { + type: 'Prompt', + properties: { + promptText: 'someText' + } + } + }, + { + id: 'prompt-with-commands', + enabled: true, + action: { + type: 'Prompt', + properties: { + promptText: 'someText', + commands: [ + { + text: 'Hello' + } + ] + } + } + } + ] + }; + + testObject = instantiationService.createInstance(TestExperimentService); + const custom = testObject.getExperimentsToRunByType(ExperimentActionType.Custom).then(result => { + assert.equal(result.length, 2); + assert.equal(result[0].id, 'simple-experiment'); + assert.equal(result[1].id, 'custom-experiment'); + }); + const prompt = testObject.getExperimentsToRunByType(ExperimentActionType.Prompt).then(result => { + assert.equal(result.length, 2); + assert.equal(result[0].id, 'prompt-with-no-commands'); + assert.equal(result[1].id, 'prompt-with-commands'); + }); + return TPromise.join([custom, prompt]); + }); + + + // test('Experiment with condition type FileEdit should increment editcount as appropriate', () => { + + // }); + + // test('Experiment with condition type WorkspaceEdit should increment editcount as appropriate', () => { + + // }); + + + +}); + + diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts index d64b5397088..7f4a45ba4be 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts @@ -42,6 +42,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { assign } from 'vs/base/common/objects'; import URI from 'vs/base/common/uri'; import { areSameExtensions, getGalleryExtensionIdFromLocal } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { IExperimentService, ExperimentActionType } from 'vs/workbench/parts/experiments/node/experimentService'; const empty: { [key: string]: any; } = Object.create(null); const milliSecondsInADay = 1000 * 60 * 60 * 24; @@ -75,6 +76,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe private _availableRecommendations: { [pattern: string]: string[] } = Object.create(null); private _allWorkspaceRecommendedExtensions: IExtensionRecommendation[] = []; private _dynamicWorkspaceRecommendations: string[] = []; + private _experimentalRecommendations: { [id: string]: string } = Object.create(null); private _allIgnoredRecommendations: string[] = []; private _globallyIgnoredRecommendations: string[] = []; private _workspaceIgnoredRecommendations: string[] = []; @@ -104,7 +106,8 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe @IViewletService private viewletService: IViewletService, @INotificationService private notificationService: INotificationService, @IExtensionManagementService private extensionManagementService: IExtensionManagementService, - @IExtensionManagementServerService private extensionManagementServiceService: IExtensionManagementServerService + @IExtensionManagementServerService private extensionManagementServiceService: IExtensionManagementServerService, + @IExperimentService private experimentService: IExperimentService, ) { super(); @@ -124,6 +127,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe // these must be called after workspace configs have been refreshed. this.getCachedDynamicWorkspaceRecommendations(); this._suggestFileBasedRecommendations(); + this.getExperimentalRecommendations(); return this._suggestWorkspaceRecommendations(); }).then(() => { this._modelService.onModelAdded(this._suggest, this, this._disposables); @@ -188,6 +192,11 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe return output; } + forEach(this._experimentalRecommendations, entry => output[entry.key.toLowerCase()] = { + reasonId: ExtensionRecommendationReason.Experimental, + reasonText: entry.value + }); + if (this.contextService.getWorkspace().folders && this.contextService.getWorkspace().folders.length === 1) { const currentRepo = this.contextService.getWorkspace().folders[0].name; this._dynamicWorkspaceRecommendations.forEach(x => output[x.toLowerCase()] = { @@ -404,7 +413,8 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe return this.fetchProactiveRecommendations().then(() => { const others = distinct([ ...Object.keys(this._exeBasedRecommendations), - ...this._dynamicWorkspaceRecommendations]); + ...this._dynamicWorkspaceRecommendations, + ...Object.keys(this._experimentalRecommendations)]); shuffle(others); return others.map(extensionId => { const sources: ExtensionRecommendationSource[] = []; @@ -935,6 +945,20 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe }); } + private getExperimentalRecommendations() { + this.experimentService.getExperimentsToRunByType(ExperimentActionType.AddToRecommendations).then(experiments => { + (experiments || []).forEach(experiment => { + if (experiment.action.properties && Array.isArray(experiment.action.properties.recommendations) && experiment.action.properties.recommendationReason) { + experiment.action.properties.recommendations.forEach(id => { + if (this.isExtensionAllowedToBeRecommended(id)) { + this._experimentalRecommendations[id] = experiment.action.properties.recommendationReason; + } + }); + } + }); + }); + } + getKeywordsForExtension(extension: string): string[] { const keywords = product.extensionKeywords || {}; return keywords[extension] || []; diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts index b22cdb46bd2..aa36590f720 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts @@ -5,6 +5,7 @@ 'use strict'; + import { localize } from 'vs/nls'; import { TPromise } from 'vs/base/common/winjs.base'; import { dispose } from 'vs/base/common/lifecycle'; @@ -39,6 +40,7 @@ import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/v import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { distinct } from 'vs/base/common/arrays'; import URI from 'vs/base/common/uri'; +import { IExperimentService } from 'vs/workbench/parts/experiments/node/experimentService'; export class ExtensionsListView extends ViewletPanel { @@ -63,7 +65,8 @@ export class ExtensionsListView extends ViewletPanel { @ITelemetryService private telemetryService: ITelemetryService, @IConfigurationService configurationService: IConfigurationService, @IWorkspaceContextService protected contextService: IWorkspaceContextService, - @IExtensionManagementServerService protected extensionManagementServerService: IExtensionManagementServerService + @IExtensionManagementServerService protected extensionManagementServerService: IExtensionManagementServerService, + @IExperimentService private experimentService: IExperimentService ) { super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService); } @@ -272,6 +275,10 @@ export class ExtensionsListView extends ViewletPanel { return this.getRecommendationsModel(query, options); } + if (/\bcurated:([^\s]+)\b/.test(query.value)) { + return this.getCuratedModel(query, options); + } + let text = query.value; const extensionRegex = /\bext:([^\s]+)\b/g; @@ -368,6 +375,21 @@ export class ExtensionsListView extends ViewletPanel { }); } + private getCuratedModel(query: Query, options: IQueryOptions): TPromise> { + const value = query.value.replace(/curated:/g, '').trim(); + return this.experimentService.getCuratedExtensionsList(value).then(names => { + if (Array.isArray(names) && names.length) { + options.source = `curated:${value}`; + return this.extensionsWorkbenchService.queryGallery(assign(options, { names, pageSize: names.length })) + .then(pager => { + this.sortFirstPage(pager, names); + return new PagedModel(pager || []); + }); + } + return TPromise.as(new PagedModel([])); + }); + } + private getRecommendationsModel(query: Query, options: IQueryOptions): TPromise> { const value = query.value.replace(/@recommended/g, '').trim().toLowerCase(); diff --git a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsTipsService.test.ts b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsTipsService.test.ts index b32f41f8f3c..faa0bf3f29f 100644 --- a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsTipsService.test.ts +++ b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsTipsService.test.ts @@ -48,6 +48,8 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { INotificationService, Severity, IPromptChoice } from 'vs/platform/notification/common/notification'; import { URLService } from 'vs/platform/url/common/urlService'; +import { IExperimentService } from 'vs/workbench/parts/experiments/node/experimentService'; +import { TestExperimentService } from 'vs/workbench/parts/experiments/test/node/experimentService.test'; const mockExtensionGallery: IGalleryExtension[] = [ aGalleryExtension('MockExtension1', { @@ -175,6 +177,7 @@ suite('ExtensionsTipsService Test', () => { didUninstallEvent: Emitter; let prompted: boolean; let onModelAddedEvent: Emitter; + let experimentService: TestExperimentService; suiteSetup(() => { instantiationService = new TestInstantiationService(); @@ -196,6 +199,9 @@ suite('ExtensionsTipsService Test', () => { instantiationService.stub(ITelemetryService, NullTelemetryService); instantiationService.stub(IURLService, URLService); + experimentService = instantiationService.createInstance(TestExperimentService); + instantiationService.stub(IExperimentService, experimentService); + onModelAddedEvent = new Emitter(); product.extensionTips = { @@ -215,6 +221,12 @@ suite('ExtensionsTipsService Test', () => { }; }); + suiteTeardown(() => { + if (experimentService) { + experimentService.dispose(); + } + }); + setup(() => { instantiationService.stub(IEnvironmentService, { extensionDevelopmentPath: false }); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', []); diff --git a/src/vs/workbench/parts/stats/node/workspaceStats.ts b/src/vs/workbench/parts/stats/node/workspaceStats.ts index 3eec8a04f22..fbe53164bd1 100644 --- a/src/vs/workbench/parts/stats/node/workspaceStats.ts +++ b/src/vs/workbench/parts/stats/node/workspaceStats.ts @@ -179,6 +179,8 @@ export class WorkspaceStats implements IWorkbenchContribution { this.reportCloudStats(); } + public static tags: Tags; + private searchArray(arr: string[], regEx: RegExp): boolean { return arr.some(v => v.search(regEx) > -1) || undefined; } @@ -372,6 +374,7 @@ export class WorkspaceStats implements IWorkbenchContribution { } */ this.telemetryService.publicLog('workspce.tags', tags); + WorkspaceStats.tags = tags; }, error => onUnexpectedError(error)); } diff --git a/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts b/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts index fe9c4ac96b0..1785ed41fd7 100644 --- a/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts +++ b/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts @@ -12,7 +12,6 @@ import { createSyncDescriptor } from 'vs/platform/instantiation/common/descripto import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; import { ISearchService } from 'vs/platform/search/common/search'; import { ITelemetryService, ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry'; -import { IExperimentService, IExperiments } from 'vs/platform/telemetry/common/experiments'; import { IUntitledEditorService, UntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import * as minimist from 'minimist'; @@ -68,11 +67,9 @@ suite.skip('QuickOpen performance (integration)', () => { const testWorkspacePath = testWorkspaceArg ? path.resolve(testWorkspaceArg) : __dirname; const telemetryService = new TestTelemetryService(); - const experimentService = new TestExperimentService(); const configurationService = new TestConfigurationService(); const instantiationService = new InstantiationService(new ServiceCollection( [ITelemetryService, telemetryService], - [IExperimentService, experimentService], [IConfigurationService, configurationService], [IModelService, new ModelServiceImpl(null, configurationService)], [IWorkspaceContextService, new TestContextService(testWorkspace(URI.file(testWorkspacePath)))], @@ -179,12 +176,3 @@ class TestTelemetryService implements ITelemetryService { }); } } - -class TestExperimentService implements IExperimentService { - - _serviceBrand: any; - - getExperiments(): IExperiments { - return {}; - } -} diff --git a/src/vs/workbench/workbench.main.ts b/src/vs/workbench/workbench.main.ts index 4bb1eda080e..f37dbb90cbc 100644 --- a/src/vs/workbench/workbench.main.ts +++ b/src/vs/workbench/workbench.main.ts @@ -140,3 +140,4 @@ import 'vs/workbench/parts/outline/electron-browser/outline.contribution'; // services import 'vs/workbench/services/bulkEdit/electron-browser/bulkEditService'; +import 'vs/workbench/parts/experiments/electron-browser/experiments.contribution'; \ No newline at end of file From 368ab9c31b5c0930a40ab3f0e0d768ebbeeb9ef8 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 26 Jun 2018 06:56:23 +0200 Subject: [PATCH 070/228] use systemPreferences.setUserDefault() to workaround #35361 --- src/vs/code/electron-main/app.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index aebb464f8d2..a262d3fb3a6 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -5,7 +5,7 @@ 'use strict'; -import { app, ipcMain as ipc } from 'electron'; +import { app, ipcMain as ipc, systemPreferences } from 'electron'; import * as platform from 'vs/base/common/platform'; import { WindowsManager } from 'vs/code/electron-main/windows'; import { IWindowsService, OpenContext, ActiveWindowManager } from 'vs/platform/windows/common/windows'; @@ -85,7 +85,7 @@ export class CodeApplication { @ILogService private logService: ILogService, @IEnvironmentService private environmentService: IEnvironmentService, @ILifecycleService private lifecycleService: ILifecycleService, - @IConfigurationService configurationService: ConfigurationService, + @IConfigurationService private configurationService: ConfigurationService, @IStateService private stateService: IStateService, @IHistoryMainService private historyMainService: IHistoryMainService ) { @@ -274,6 +274,20 @@ export class CodeApplication { app.setAppUserModelId(product.win32AppUserModelId); } + // Fix native tabs on macOS 10.13 + // macOS enables a compatibility patch for any bundle ID beginning with + // "com.microsoft.", which breaks native tabs for VS Code when using this + // identifier (from the official build). + // Explicitly opt out of the patch here before creating any windows. + // See: https://github.com/Microsoft/vscode/issues/35361#issuecomment-399794085 + try { + if (platform.isMacintosh && this.configurationService.getValue('window.nativeTabs') === true && !systemPreferences.getUserDefault('NSUseImprovedLayoutPass', 'boolean')) { + systemPreferences.setUserDefault('NSUseImprovedLayoutPass', 'boolean', true as any); + } + } catch (error) { + this.logService.error(error); + } + // Create Electron IPC Server this.electronIpcServer = new ElectronIPCServer(); From 1ec986b06b6325b6da45c62daefc1415b1760831 Mon Sep 17 00:00:00 2001 From: Leonardo Braga Date: Tue, 26 Jun 2018 01:02:14 -0400 Subject: [PATCH 071/228] Fixed the creation of UNC 'file' URIs from 'untitled' ones (#52518) --- .../parts/editor/editor.contribution.ts | 2 +- .../files/electron-browser/fileCommands.ts | 2 +- .../textfile/common/textFileService.ts | 10 +++-- .../textfile/test/textFileService.test.ts | 41 +++++++++++++++++-- 4 files changed, 46 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 73c180588f7..342d3af6837 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -121,7 +121,7 @@ class UntitledEditorInputFactory implements IEditorInputFactory { let resource = untitledEditorInput.getResource(); if (untitledEditorInput.hasAssociatedFilePath) { - resource = URI.file(resource.fsPath); // untitled with associated file path use the file schema + resource = resource.with({ scheme: Schemas.file }); // untitled with associated file path use the file schema } const serialized: ISerializedUntitledEditorInput = { diff --git a/src/vs/workbench/parts/files/electron-browser/fileCommands.ts b/src/vs/workbench/parts/files/electron-browser/fileCommands.ts index e94a76efe38..77a19b2799a 100644 --- a/src/vs/workbench/parts/files/electron-browser/fileCommands.ts +++ b/src/vs/workbench/parts/files/electron-browser/fileCommands.ts @@ -111,7 +111,7 @@ function save(resource: URI, isSaveAs: boolean, editorService: IEditorService, f if (!isSaveAs && resource.scheme === Schemas.untitled && untitledEditorService.hasAssociatedFilePath(resource)) { savePromise = textFileService.save(resource).then((result) => { if (result) { - return URI.file(resource.fsPath); + return resource.with({ scheme: Schemas.file }); } return null; diff --git a/src/vs/workbench/services/textfile/common/textFileService.ts b/src/vs/workbench/services/textfile/common/textFileService.ts index 0e4d700b9d0..d8fe633c1a0 100644 --- a/src/vs/workbench/services/textfile/common/textFileService.ts +++ b/src/vs/workbench/services/textfile/common/textFileService.ts @@ -439,16 +439,16 @@ export abstract class TextFileService implements ITextFileService { for (let i = 0; i < untitledResources.length; i++) { const untitled = untitledResources[i]; if (this.untitledEditorService.exists(untitled)) { - let targetPath: string; + let targetUri: URI; // Untitled with associated file path don't need to prompt if (this.untitledEditorService.hasAssociatedFilePath(untitled)) { - targetPath = untitled.fsPath; + targetUri = untitled.with({ scheme: Schemas.file }); } // Otherwise ask user else { - targetPath = await this.promptForPath(this.suggestFileName(untitled)); + const targetPath = await this.promptForPath(this.suggestFileName(untitled)); if (!targetPath) { return TPromise.as({ results: [...fileResources, ...untitledResources].map(r => { @@ -458,9 +458,11 @@ export abstract class TextFileService implements ITextFileService { }) }); } + + targetUri = URI.file(targetPath); } - targetsForUntitled.push(URI.file(targetPath)); + targetsForUntitled.push(targetUri); } } diff --git a/src/vs/workbench/services/textfile/test/textFileService.test.ts b/src/vs/workbench/services/textfile/test/textFileService.test.ts index 216f1742075..310d0a93e44 100644 --- a/src/vs/workbench/services/textfile/test/textFileService.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileService.test.ts @@ -5,10 +5,12 @@ 'use strict'; import * as assert from 'assert'; +import * as sinon from 'sinon'; import * as platform from 'vs/base/common/platform'; +import URI from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import { ILifecycleService, ShutdownEvent, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; -import { workbenchInstantiationService, TestLifecycleService, TestTextFileService, TestWindowsService, TestContextService } from 'vs/workbench/test/workbenchTestServices'; +import { workbenchInstantiationService, TestLifecycleService, TestTextFileService, TestWindowsService, TestContextService, TestFileService } from 'vs/workbench/test/workbenchTestServices'; import { toResource } from 'vs/base/test/common/utils'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IWindowsService } from 'vs/platform/windows/common/windows'; @@ -17,9 +19,12 @@ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfile import { ConfirmResult } from 'vs/workbench/common/editor'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel'; -import { HotExitConfiguration } from 'vs/platform/files/common/files'; +import { HotExitConfiguration, IFileService } from 'vs/platform/files/common/files'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; import { IWorkspaceContextService, Workspace } from 'vs/platform/workspace/common/workspace'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; +import { Schemas } from 'vs/base/common/network'; class ServiceAccessor { constructor( @@ -27,7 +32,9 @@ class ServiceAccessor { @ITextFileService public textFileService: TestTextFileService, @IUntitledEditorService public untitledEditorService: IUntitledEditorService, @IWindowsService public windowsService: TestWindowsService, - @IWorkspaceContextService public contextService: TestContextService + @IWorkspaceContextService public contextService: TestContextService, + @IModelService public modelService: ModelServiceImpl, + @IFileService public fileService: TestFileService ) { } } @@ -194,6 +201,34 @@ suite('Files - TextFileService', () => { }); }); + test('save - UNC path', function () { + const untitledUncUri = URI.from({ scheme: 'untitled', authority: 'server', path: '/share/path/file.txt' }); + model = instantiationService.createInstance(TextFileEditorModel, untitledUncUri, 'utf8'); + (accessor.textFileService.models).add(model.getResource(), model); + + const mockedFileUri = untitledUncUri.with({ scheme: Schemas.file }); + const mockedEditorInput = instantiationService.createInstance(TextFileEditorModel, mockedFileUri, 'utf8'); + const loadOrCreateStub = sinon.stub(accessor.textFileService.models, 'loadOrCreate', () => TPromise.wrap(mockedEditorInput)); + + sinon.stub(accessor.untitledEditorService, 'exists', () => true); + sinon.stub(accessor.untitledEditorService, 'hasAssociatedFilePath', () => true); + sinon.stub(accessor.modelService, 'updateModel', () => { }); + + return model.load().then(() => { + model.textEditorModel.setValue('foo'); + + return accessor.textFileService.saveAll(true).then(res => { + assert.ok(loadOrCreateStub.calledOnce); + assert.equal(res.results.length, 1); + assert.ok(res.results[0].success); + + assert.equal(res.results[0].target.scheme, Schemas.file); + assert.equal(res.results[0].target.authority, untitledUncUri.authority); + assert.equal(res.results[0].target.path, untitledUncUri.path); + }); + }); + }); + test('saveAll - file', function () { model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8'); (accessor.textFileService.models).add(model.getResource(), model); From 51e0ad1ac7f96466130c413dddf11a8934b3a007 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 25 Jun 2018 22:29:26 -0700 Subject: [PATCH 072/228] Settings editor - fix setting float values. #50249 --- src/vs/workbench/parts/preferences/browser/settingsTree.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/preferences/browser/settingsTree.ts b/src/vs/workbench/parts/preferences/browser/settingsTree.ts index c3d2c30f77d..6d82e55449d 100644 --- a/src/vs/workbench/parts/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/parts/preferences/browser/settingsTree.ts @@ -702,7 +702,8 @@ export class SettingsRenderer implements IRenderer { this.renderText(element, isSelected, template, valueControlElement, onChange); } else if (element.valueType === 'number' || element.valueType === 'integer') { valueControlElement.classList.add('setting-type-number'); - this.renderText(element, isSelected, template, valueControlElement, value => onChange(parseInt(value))); + const parseFn = element.valueType === 'integer' ? parseInt : parseFloat; + this.renderText(element, isSelected, template, valueControlElement, value => onChange(parseFn(value))); } else { valueControlElement.classList.add('setting-type-complex'); this.renderEditInSettingsJson(element, isSelected, template, valueControlElement); From 22d368628097da2d94c7f53895d02385322f7636 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 26 Jun 2018 11:37:37 +0200 Subject: [PATCH 073/228] fix #52893 --- .../browser/parts/editor/editorGroupView.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 012077d6b2d..23253584873 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -1278,14 +1278,15 @@ export class EditorGroupView extends Themable implements IEditorGroupView { if (activeReplacement) { // Open replacement as active editor - return this.doOpenEditor(activeReplacement.replacement, activeReplacement.options).then(() => { + const openEditorResult = this.doOpenEditor(activeReplacement.replacement, activeReplacement.options); - // Close previous active editor - this.doCloseInactiveEditor(activeReplacement.editor); + // Close previous active editor + this.doCloseInactiveEditor(activeReplacement.editor); - // Forward to title control - this.titleAreaControl.closeEditor(activeReplacement.editor); - }); + // Forward to title control + this.titleAreaControl.closeEditor(activeReplacement.editor); + + return openEditorResult; } return TPromise.as(void 0); From 45eb2dcc4af7cc8c2c265bc2f88f88bcd474b868 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 26 Jun 2018 12:28:18 +0200 Subject: [PATCH 074/228] [json] problem with comment after literal. For #52523 --- src/vs/base/common/json.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/base/common/json.ts b/src/vs/base/common/json.ts index ffa75f46095..300eae4eb7e 100644 --- a/src/vs/base/common/json.ts +++ b/src/vs/base/common/json.ts @@ -407,6 +407,7 @@ export function createScanner(text: string, ignoreTrivia: boolean = false): JSON case CharacterCodes.doubleQuote: case CharacterCodes.colon: case CharacterCodes.comma: + case CharacterCodes.slash: return false; } return true; From 91b3e3e8d55136b8236d85196786d4807a6db36e Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 26 Jun 2018 12:35:35 +0200 Subject: [PATCH 075/228] [json] allow trailing comma by default --- src/vs/base/common/json.ts | 8 ++++---- src/vs/base/test/common/json.test.ts | 10 +++++++--- .../configuration/node/configurationEditingService.ts | 2 +- .../services/configuration/node/jsonEditingService.ts | 2 +- .../services/keybinding/common/keybindingEditing.ts | 2 +- 5 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/vs/base/common/json.ts b/src/vs/base/common/json.ts index 300eae4eb7e..98a6a6f68c9 100644 --- a/src/vs/base/common/json.ts +++ b/src/vs/base/common/json.ts @@ -629,7 +629,7 @@ export type JSONPath = Segment[]; export interface ParseOptions { disallowComments?: boolean; - allowTrailingComma?: boolean; + disallowTrailingComma?: boolean; } /** @@ -818,7 +818,7 @@ export function visit(text: string, visitor: JSONVisitor, options?: ParseOptions onError = toOneArgVisit(visitor.onError); let disallowComments = options && options.disallowComments; - let allowTrailingComma = options && options.allowTrailingComma; + let disallowTrailingComma = options && options.disallowTrailingComma; function scanNext(): SyntaxKind { while (true) { let token = _scanner.scan(); @@ -930,7 +930,7 @@ export function visit(text: string, visitor: JSONVisitor, options?: ParseOptions } onSeparator(','); scanNext(); // consume comma - if (_scanner.getToken() === SyntaxKind.CloseBraceToken && allowTrailingComma) { + if (_scanner.getToken() === SyntaxKind.CloseBraceToken && !disallowTrailingComma) { break; } } else if (needsComma) { @@ -962,7 +962,7 @@ export function visit(text: string, visitor: JSONVisitor, options?: ParseOptions } onSeparator(','); scanNext(); // consume comma - if (_scanner.getToken() === SyntaxKind.CloseBracketToken && allowTrailingComma) { + if (_scanner.getToken() === SyntaxKind.CloseBracketToken && !disallowTrailingComma) { break; } } else if (needsComma) { diff --git a/src/vs/base/test/common/json.test.ts b/src/vs/base/test/common/json.test.ts index 2551f00fde3..3606713aa3b 100644 --- a/src/vs/base/test/common/json.test.ts +++ b/src/vs/base/test/common/json.test.ts @@ -229,15 +229,19 @@ suite('JSON', () => { }); test('parse: trailing comma', () => { - let options = { allowTrailingComma: true }; + // default is allow + assertValidParse('{ "hello": [], }', { hello: [] }); + + let options = { disallowTrailingComma: false }; assertValidParse('{ "hello": [], }', { hello: [] }, options); assertValidParse('{ "hello": [] }', { hello: [] }, options); assertValidParse('{ "hello": [], "world": {}, }', { hello: [], world: {} }, options); assertValidParse('{ "hello": [], "world": {} }', { hello: [], world: {} }, options); assertValidParse('{ "hello": [1,] }', { hello: [1] }, options); - assertInvalidParse('{ "hello": [], }', { hello: [] }); - assertInvalidParse('{ "hello": [], "world": {}, }', { hello: [], world: {} }); + options = { disallowTrailingComma: true }; + assertInvalidParse('{ "hello": [], }', { hello: [] }, options); + assertInvalidParse('{ "hello": [], "world": {}, }', { hello: [], world: {} }, options); }); test('tree: literals', () => { diff --git a/src/vs/workbench/services/configuration/node/configurationEditingService.ts b/src/vs/workbench/services/configuration/node/configurationEditingService.ts index ffea6529bbf..cc274bdbdb2 100644 --- a/src/vs/workbench/services/configuration/node/configurationEditingService.ts +++ b/src/vs/workbench/services/configuration/node/configurationEditingService.ts @@ -371,7 +371,7 @@ export class ConfigurationEditingService { return false; } const parseErrors: json.ParseError[] = []; - json.parse(model.getValue(), parseErrors, { allowTrailingComma: true }); + json.parse(model.getValue(), parseErrors); return parseErrors.length > 0; } diff --git a/src/vs/workbench/services/configuration/node/jsonEditingService.ts b/src/vs/workbench/services/configuration/node/jsonEditingService.ts index 2c47d99e7c6..13f2fe32f22 100644 --- a/src/vs/workbench/services/configuration/node/jsonEditingService.ts +++ b/src/vs/workbench/services/configuration/node/jsonEditingService.ts @@ -95,7 +95,7 @@ export class JSONEditingService implements IJSONEditingService { private hasParseErrors(model: ITextModel): boolean { const parseErrors: json.ParseError[] = []; - json.parse(model.getValue(), parseErrors, { allowTrailingComma: true }); + json.parse(model.getValue(), parseErrors); return parseErrors.length > 0; } diff --git a/src/vs/workbench/services/keybinding/common/keybindingEditing.ts b/src/vs/workbench/services/keybinding/common/keybindingEditing.ts index 98b80945993..49bfd52bada 100644 --- a/src/vs/workbench/services/keybinding/common/keybindingEditing.ts +++ b/src/vs/workbench/services/keybinding/common/keybindingEditing.ts @@ -255,7 +255,7 @@ export class KeybindingsEditingService extends Disposable implements IKeybinding private parse(model: ITextModel): { result: IUserFriendlyKeybinding[], parseErrors: json.ParseError[] } { const parseErrors: json.ParseError[] = []; - const result = json.parse(model.getValue(), parseErrors, { allowTrailingComma: true }); + const result = json.parse(model.getValue(), parseErrors); return { result, parseErrors }; } From 31a0961ced957d6f5a8d38edbe799a273b8cdbfb Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 26 Jun 2018 12:36:08 +0200 Subject: [PATCH 076/228] remove unnecessary try catch aroud json.parse --- .../workspaces/electron-main/workspacesMainService.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/vs/platform/workspaces/electron-main/workspacesMainService.ts b/src/vs/platform/workspaces/electron-main/workspacesMainService.ts index a339d3efafb..ee75f03a42a 100644 --- a/src/vs/platform/workspaces/electron-main/workspacesMainService.ts +++ b/src/vs/platform/workspaces/electron-main/workspacesMainService.ts @@ -96,12 +96,7 @@ export class WorkspacesMainService implements IWorkspacesMainService { private doParseStoredWorkspace(path: string, contents: string): IStoredWorkspace { // Parse workspace file - let storedWorkspace: IStoredWorkspace; - try { - storedWorkspace = json.parse(contents); // use fault tolerant parser - } catch (error) { - throw new Error(`${path} cannot be parsed as JSON file (${error}).`); - } + let storedWorkspace: IStoredWorkspace = json.parse(contents); // use fault tolerant parser // Filter out folders which do not have a path or uri set if (Array.isArray(storedWorkspace.folders)) { From d8c767b4d108e7ebf0d4c8ba324e4108a49b42d7 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 26 Jun 2018 12:36:46 +0200 Subject: [PATCH 077/228] [themes] report theme loading problems when switching themes --- .../parts/themes/electron-browser/themes.contribution.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/workbench/parts/themes/electron-browser/themes.contribution.ts b/src/vs/workbench/parts/themes/electron-browser/themes.contribution.ts index cc04c06a371..bde131d66ed 100644 --- a/src/vs/workbench/parts/themes/electron-browser/themes.contribution.ts +++ b/src/vs/workbench/parts/themes/electron-browser/themes.contribution.ts @@ -26,6 +26,7 @@ import { Color } from 'vs/base/common/color'; import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { LIGHT, DARK, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService'; import { schemaId } from 'vs/workbench/services/themes/common/colorThemeSchema'; +import { onUnexpectedError } from 'vs/base/common/errors'; export class SelectColorThemeAction extends Action { @@ -67,6 +68,7 @@ export class SelectColorThemeAction extends Action { this.themeService.setColorTheme(theme.id, target).done(null, err => { + onUnexpectedError(err); this.themeService.setColorTheme(currentTheme.id, null); } ); @@ -125,6 +127,7 @@ class SelectIconThemeAction extends Action { } this.themeService.setFileIconTheme(theme && theme.id, target).done(null, err => { + onUnexpectedError(err); this.themeService.setFileIconTheme(currentTheme.id, null); } ); From 4e2a84b5f708fc1b17a1054fad0024ba7814f42f Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 26 Jun 2018 12:39:34 +0200 Subject: [PATCH 078/228] light theme: remove trailing comma --- extensions/theme-defaults/themes/light_vs.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/theme-defaults/themes/light_vs.json b/extensions/theme-defaults/themes/light_vs.json index a73b42a6c89..23d74f67aa7 100644 --- a/extensions/theme-defaults/themes/light_vs.json +++ b/extensions/theme-defaults/themes/light_vs.json @@ -292,7 +292,7 @@ { "scope": [ "support.type.property-name.json", - "support.type.property-name.jsonc", + "support.type.property-name.jsonc" ], "settings": { "foreground": "#0451a5" From 923e9ccb3fc44bfaaf4b4049015c84e42bc40f95 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 26 Jun 2018 14:48:15 +0200 Subject: [PATCH 079/228] [json] fix test failures --- src/vs/base/test/common/json.test.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/vs/base/test/common/json.test.ts b/src/vs/base/test/common/json.test.ts index 3606713aa3b..da9feea768c 100644 --- a/src/vs/base/test/common/json.test.ts +++ b/src/vs/base/test/common/json.test.ts @@ -43,9 +43,9 @@ function assertInvalidParse(input: string, expected: any, options?: ParseOptions assert.deepEqual(actual, expected); } -function assertTree(input: string, expected: any, expectedErrors: number[] = []): void { +function assertTree(input: string, expected: any, expectedErrors: number[] = [], options?: ParseOptions): void { var errors: ParseError[] = []; - var actual = parseTree(input, errors); + var actual = parseTree(input, errors, options); assert.deepEqual(errors.map(e => e.error, expected), expectedErrors); let checkParent = (node: Node) => { @@ -203,7 +203,7 @@ suite('JSON', () => { test('parse: objects with errors', () => { assertInvalidParse('{,}', {}); - assertInvalidParse('{ "foo": true, }', { foo: true }); + assertInvalidParse('{ "foo": true, }', { foo: true }, { disallowTrailingComma: true }); assertInvalidParse('{ "bar": 8 "xoo": "foo" }', { bar: 8, xoo: 'foo' }); assertInvalidParse('{ ,"bar": 8 }', { bar: 8 }); assertInvalidParse('{ ,"bar": 8, "foo" }', { bar: 8 }); @@ -213,10 +213,10 @@ suite('JSON', () => { test('parse: array with errors', () => { assertInvalidParse('[,]', []); - assertInvalidParse('[ 1, 2, ]', [1, 2]); + assertInvalidParse('[ 1, 2, ]', [1, 2], { disallowTrailingComma: true }); assertInvalidParse('[ 1 2, 3 ]', [1, 2, 3]); assertInvalidParse('[ ,1, 2, 3 ]', [1, 2, 3]); - assertInvalidParse('[ ,1, 2, 3, ]', [1, 2, 3]); + assertInvalidParse('[ ,1, 2, 3, ]', [1, 2, 3], { disallowTrailingComma: true }); }); test('parse: disallow commments', () => { @@ -324,6 +324,6 @@ suite('JSON', () => { } ] } - , [ParseErrorCode.PropertyNameExpected, ParseErrorCode.ValueExpected]); + , [ParseErrorCode.PropertyNameExpected, ParseErrorCode.ValueExpected], { disallowTrailingComma: true }); }); }); From 4010adc3fb75324b1b178eca8f60323c17e9ff56 Mon Sep 17 00:00:00 2001 From: isidor Date: Tue, 26 Jun 2018 15:38:29 +0200 Subject: [PATCH 080/228] update distro commit hash --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4eeb6524732..a770c00f3cf 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.25.0", - "distro": "974512091c88e4e435fc2a427cf44b08e61a57dd", + "distro": "4e28ae25f1ed59b39382085f04b3514c2b6202cc", "author": { "name": "Microsoft Corporation" }, From 396ca1f36f13dcee54abe30df8eab8f6a63835fe Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 26 Jun 2018 07:29:32 -0700 Subject: [PATCH 081/228] Improve experimentalTextureCachingStrategy comment Fixes #52890 --- .../parts/terminal/electron-browser/terminal.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts b/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts index 6575b3d8130..84965da9af4 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts @@ -347,7 +347,7 @@ configurationRegistry.registerConfiguration({ 'default': false }, 'terminal.integrated.experimentalTextureCachingStrategy': { - 'description': nls.localize('terminal.integrated.experimentalTextureCachingStrategy', "Controls how the terminal stores glyph textures. Changes to this setting will only apply to new terminals."), + 'description': nls.localize('terminal.integrated.experimentalTextureCachingStrategy', "Controls how the terminal stores glyph textures. `static` is the default and uses a fixed image to draw the characters from. `dynamic` will draw the characters to the texture as they are needed, this should boost overall performance at the cost of slightly increased draw time the first time a character is drawn. `dynamic` will eventually become the default and this setting will be removed. Changes to this setting will only apply to new terminals."), 'type': 'string', 'enum': ['static', 'dynamic'], 'default': 'static' From 8adcb8b45b34657c724360d79d341ec50497bad5 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 26 Jun 2018 07:42:47 -0700 Subject: [PATCH 082/228] More tweaks to texture setting comment Fixes #52890 --- .../parts/terminal/electron-browser/terminal.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts b/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts index 84965da9af4..d85a4c3c02a 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts @@ -347,7 +347,7 @@ configurationRegistry.registerConfiguration({ 'default': false }, 'terminal.integrated.experimentalTextureCachingStrategy': { - 'description': nls.localize('terminal.integrated.experimentalTextureCachingStrategy', "Controls how the terminal stores glyph textures. `static` is the default and uses a fixed image to draw the characters from. `dynamic` will draw the characters to the texture as they are needed, this should boost overall performance at the cost of slightly increased draw time the first time a character is drawn. `dynamic` will eventually become the default and this setting will be removed. Changes to this setting will only apply to new terminals."), + 'description': nls.localize('terminal.integrated.experimentalTextureCachingStrategy', "Controls how the terminal stores glyph textures. `static` is the default and uses a fixed texture to draw the characters from. `dynamic` will draw the characters to the texture as they are needed, this should boost overall performance at the cost of slightly increased draw time the first time a character is drawn. `dynamic` will eventually become the default and this setting will be removed. Changes to this setting will only apply to new terminals."), 'type': 'string', 'enum': ['static', 'dynamic'], 'default': 'static' From f3e89cad5f6ca0fb3e97d37208de188ca49ac7bb Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 26 Jun 2018 07:50:51 -0700 Subject: [PATCH 083/228] Rename terminal ext APIS to match convention Fixes 52882 --- src/vs/vscode.proposed.d.ts | 12 ++++++------ src/vs/workbench/api/node/extHostTerminalService.ts | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index e342923c195..968306e710f 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -341,7 +341,7 @@ declare module 'vscode' { * provides access to the raw data stream from the process running within the terminal, * including VT sequences. */ - onData: Event; + onDidWriteData: Event; } /** @@ -368,10 +368,10 @@ declare module 'vscode' { * created with all its APIs available for use by extensions. When using the Terminal object * of a TerminalRenderer it acts just like normal only the extension that created the * TerminalRenderer essentially acts as a process. For example when an - * [Terminal.onData](#Terminal.onData) listener is registered, that will fire when - * [TerminalRenderer.write](#TerminalRenderer.write) is called. Similarly when + * [Terminal.onDidWriteData](#Terminal.onDidWriteData) listener is registered, that will fire + * when [TerminalRenderer.write](#TerminalRenderer.write) is called. Similarly when * [Terminal.sendText](#Terminal.sendText) is triggered that will fire the - * [TerminalRenderer.onInput](#TerminalRenderer.onInput) event. + * [TerminalRenderer.onDidAcceptInput](#TerminalRenderer.onDidAcceptInput) event. * * **Example:** Create a terminal renderer, show it and write hello world in red * ```typescript @@ -441,13 +441,13 @@ declare module 'vscode' { * workbench command such as `workbench.action.terminal.runSelectedText` * ```typescript * const terminalRenderer = window.createTerminalRenderer('test'); - * terminalRenderer.onInput(data => { + * terminalRenderer.onDidAcceptInput(data => { * cosole.log(data); // 'Hello world' * }); * terminalRenderer.terminal.then(t => t.sendText('Hello world')); * ``` */ - onInput: Event; + onDidAcceptInput: Event; /** * An event which fires when the [maximum dimensions](#TerminalRenderer.maimumDimensions) of diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index 5589c538331..47d2ac74ad4 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -77,7 +77,7 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi private _pidPromiseComplete: (value: number) => any; private readonly _onData: Emitter = new Emitter(); - public get onData(): Event { + public get onDidWriteData(): Event { // Tell the main side to start sending data if it's not already this._proxy.$registerOnDataListener(this._id); return this._onData && this._onData.event; @@ -156,7 +156,7 @@ export class ExtHostTerminalRenderer extends BaseExtHostTerminal implements vsco } private readonly _onInput: Emitter = new Emitter(); - public get onInput(): Event { + public get onDidAcceptInput(): Event { this._checkDisposed(); this._queueApiRequest(this._proxy.$terminalRendererRegisterOnInputListener, [this._id]); // Tell the main side to start sending data if it's not already From e7c0f075302eef6f8c72f3b35966374b6f6fc4b4 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 26 Jun 2018 17:13:16 +0200 Subject: [PATCH 084/228] fix colorize test failures --- .../test/colorize-results/tsconfig_json.json | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/extensions/typescript-basics/test/colorize-results/tsconfig_json.json b/extensions/typescript-basics/test/colorize-results/tsconfig_json.json index e9dc59f3db4..0fbf1b4c28c 100644 --- a/extensions/typescript-basics/test/colorize-results/tsconfig_json.json +++ b/extensions/typescript-basics/test/colorize-results/tsconfig_json.json @@ -1,7 +1,7 @@ [ { "c": "{", - "t": "source.json.jsonc meta.structure.dictionary.json.jsonc punctuation.definition.dictionary.begin.json.jsonc", + "t": "source.jsonc meta.structure.dictionary.jsonc punctuation.definition.dictionary.begin.jsonc", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -12,7 +12,7 @@ }, { "c": "\t", - "t": "source.json.jsonc meta.structure.dictionary.json.jsonc", + "t": "source.jsonc meta.structure.dictionary.jsonc", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -23,40 +23,40 @@ }, { "c": "\"", - "t": "source.json.jsonc meta.structure.dictionary.json.jsonc string.json.jsonc support.type.property-name.json.jsonc punctuation.support.type.property-name.begin.json.jsonc", + "t": "source.jsonc meta.structure.dictionary.jsonc string.jsonc support.type.property-name.jsonc punctuation.support.type.property-name.begin.jsonc", "r": { "dark_plus": "support.type.property-name: #9CDCFE", - "light_plus": "support.type.property-name.json: #0451A5", + "light_plus": "support.type.property-name.jsonc: #0451A5", "dark_vs": "support.type.property-name: #9CDCFE", - "light_vs": "support.type.property-name.json: #0451A5", + "light_vs": "support.type.property-name.jsonc: #0451A5", "hc_black": "support.type.property-name: #D4D4D4" } }, { "c": "compilerOptions", - "t": "source.json.jsonc meta.structure.dictionary.json.jsonc string.json.jsonc support.type.property-name.json.jsonc", + "t": "source.jsonc meta.structure.dictionary.jsonc string.jsonc support.type.property-name.jsonc", "r": { "dark_plus": "support.type.property-name: #9CDCFE", - "light_plus": "support.type.property-name.json: #0451A5", + "light_plus": "support.type.property-name.jsonc: #0451A5", "dark_vs": "support.type.property-name: #9CDCFE", - "light_vs": "support.type.property-name.json: #0451A5", + "light_vs": "support.type.property-name.jsonc: #0451A5", "hc_black": "support.type.property-name: #D4D4D4" } }, { "c": "\"", - "t": "source.json.jsonc meta.structure.dictionary.json.jsonc string.json.jsonc support.type.property-name.json.jsonc punctuation.support.type.property-name.end.json.jsonc", + "t": "source.jsonc meta.structure.dictionary.jsonc string.jsonc support.type.property-name.jsonc punctuation.support.type.property-name.end.jsonc", "r": { "dark_plus": "support.type.property-name: #9CDCFE", - "light_plus": "support.type.property-name.json: #0451A5", + "light_plus": "support.type.property-name.jsonc: #0451A5", "dark_vs": "support.type.property-name: #9CDCFE", - "light_vs": "support.type.property-name.json: #0451A5", + "light_vs": "support.type.property-name.jsonc: #0451A5", "hc_black": "support.type.property-name: #D4D4D4" } }, { "c": ":", - "t": "source.json.jsonc meta.structure.dictionary.json.jsonc meta.structure.dictionary.value.json.jsonc punctuation.separator.dictionary.key-value.json.jsonc", + "t": "source.jsonc meta.structure.dictionary.jsonc meta.structure.dictionary.value.jsonc punctuation.separator.dictionary.key-value.jsonc", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -67,7 +67,7 @@ }, { "c": " ", - "t": "source.json.jsonc meta.structure.dictionary.json.jsonc meta.structure.dictionary.value.json.jsonc", + "t": "source.jsonc meta.structure.dictionary.jsonc meta.structure.dictionary.value.jsonc", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -78,7 +78,7 @@ }, { "c": "{", - "t": "source.json.jsonc meta.structure.dictionary.json.jsonc meta.structure.dictionary.value.json.jsonc meta.structure.dictionary.json.jsonc punctuation.definition.dictionary.begin.json.jsonc", + "t": "source.jsonc meta.structure.dictionary.jsonc meta.structure.dictionary.value.jsonc meta.structure.dictionary.jsonc punctuation.definition.dictionary.begin.jsonc", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -89,7 +89,7 @@ }, { "c": "\t\t", - "t": "source.json.jsonc meta.structure.dictionary.json.jsonc meta.structure.dictionary.value.json.jsonc meta.structure.dictionary.json.jsonc", + "t": "source.jsonc meta.structure.dictionary.jsonc meta.structure.dictionary.value.jsonc meta.structure.dictionary.jsonc", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -100,40 +100,40 @@ }, { "c": "\"", - "t": "source.json.jsonc meta.structure.dictionary.json.jsonc meta.structure.dictionary.value.json.jsonc meta.structure.dictionary.json.jsonc string.json.jsonc support.type.property-name.json.jsonc punctuation.support.type.property-name.begin.json.jsonc", + "t": "source.jsonc meta.structure.dictionary.jsonc meta.structure.dictionary.value.jsonc meta.structure.dictionary.jsonc string.jsonc support.type.property-name.jsonc punctuation.support.type.property-name.begin.jsonc", "r": { "dark_plus": "support.type.property-name: #9CDCFE", - "light_plus": "support.type.property-name.json: #0451A5", + "light_plus": "support.type.property-name.jsonc: #0451A5", "dark_vs": "support.type.property-name: #9CDCFE", - "light_vs": "support.type.property-name.json: #0451A5", + "light_vs": "support.type.property-name.jsonc: #0451A5", "hc_black": "support.type.property-name: #D4D4D4" } }, { "c": "target", - "t": "source.json.jsonc meta.structure.dictionary.json.jsonc meta.structure.dictionary.value.json.jsonc meta.structure.dictionary.json.jsonc string.json.jsonc support.type.property-name.json.jsonc", + "t": "source.jsonc meta.structure.dictionary.jsonc meta.structure.dictionary.value.jsonc meta.structure.dictionary.jsonc string.jsonc support.type.property-name.jsonc", "r": { "dark_plus": "support.type.property-name: #9CDCFE", - "light_plus": "support.type.property-name.json: #0451A5", + "light_plus": "support.type.property-name.jsonc: #0451A5", "dark_vs": "support.type.property-name: #9CDCFE", - "light_vs": "support.type.property-name.json: #0451A5", + "light_vs": "support.type.property-name.jsonc: #0451A5", "hc_black": "support.type.property-name: #D4D4D4" } }, { "c": "\"", - "t": "source.json.jsonc meta.structure.dictionary.json.jsonc meta.structure.dictionary.value.json.jsonc meta.structure.dictionary.json.jsonc string.json.jsonc support.type.property-name.json.jsonc punctuation.support.type.property-name.end.json.jsonc", + "t": "source.jsonc meta.structure.dictionary.jsonc meta.structure.dictionary.value.jsonc meta.structure.dictionary.jsonc string.jsonc support.type.property-name.jsonc punctuation.support.type.property-name.end.jsonc", "r": { "dark_plus": "support.type.property-name: #9CDCFE", - "light_plus": "support.type.property-name.json: #0451A5", + "light_plus": "support.type.property-name.jsonc: #0451A5", "dark_vs": "support.type.property-name: #9CDCFE", - "light_vs": "support.type.property-name.json: #0451A5", + "light_vs": "support.type.property-name.jsonc: #0451A5", "hc_black": "support.type.property-name: #D4D4D4" } }, { "c": ":", - "t": "source.json.jsonc meta.structure.dictionary.json.jsonc meta.structure.dictionary.value.json.jsonc meta.structure.dictionary.json.jsonc meta.structure.dictionary.value.json.jsonc punctuation.separator.dictionary.key-value.json.jsonc", + "t": "source.jsonc meta.structure.dictionary.jsonc meta.structure.dictionary.value.jsonc meta.structure.dictionary.jsonc meta.structure.dictionary.value.jsonc punctuation.separator.dictionary.key-value.jsonc", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -144,7 +144,7 @@ }, { "c": " ", - "t": "source.json.jsonc meta.structure.dictionary.json.jsonc meta.structure.dictionary.value.json.jsonc meta.structure.dictionary.json.jsonc meta.structure.dictionary.value.json.jsonc", + "t": "source.jsonc meta.structure.dictionary.jsonc meta.structure.dictionary.value.jsonc meta.structure.dictionary.jsonc meta.structure.dictionary.value.jsonc", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -155,7 +155,7 @@ }, { "c": "\"", - "t": "source.json.jsonc meta.structure.dictionary.json.jsonc meta.structure.dictionary.value.json.jsonc meta.structure.dictionary.json.jsonc meta.structure.dictionary.value.json.jsonc string.quoted.double.json.jsonc punctuation.definition.string.begin.json.jsonc", + "t": "source.jsonc meta.structure.dictionary.jsonc meta.structure.dictionary.value.jsonc meta.structure.dictionary.jsonc meta.structure.dictionary.value.jsonc string.quoted.double.jsonc punctuation.definition.string.begin.jsonc", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -166,7 +166,7 @@ }, { "c": "es6", - "t": "source.json.jsonc meta.structure.dictionary.json.jsonc meta.structure.dictionary.value.json.jsonc meta.structure.dictionary.json.jsonc meta.structure.dictionary.value.json.jsonc string.quoted.double.json.jsonc", + "t": "source.jsonc meta.structure.dictionary.jsonc meta.structure.dictionary.value.jsonc meta.structure.dictionary.jsonc meta.structure.dictionary.value.jsonc string.quoted.double.jsonc", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -177,7 +177,7 @@ }, { "c": "\"", - "t": "source.json.jsonc meta.structure.dictionary.json.jsonc meta.structure.dictionary.value.json.jsonc meta.structure.dictionary.json.jsonc meta.structure.dictionary.value.json.jsonc string.quoted.double.json.jsonc punctuation.definition.string.end.json.jsonc", + "t": "source.jsonc meta.structure.dictionary.jsonc meta.structure.dictionary.value.jsonc meta.structure.dictionary.jsonc meta.structure.dictionary.value.jsonc string.quoted.double.jsonc punctuation.definition.string.end.jsonc", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -188,7 +188,7 @@ }, { "c": "\t", - "t": "source.json.jsonc meta.structure.dictionary.json.jsonc meta.structure.dictionary.value.json.jsonc meta.structure.dictionary.json.jsonc meta.structure.dictionary.value.json.jsonc", + "t": "source.jsonc meta.structure.dictionary.jsonc meta.structure.dictionary.value.jsonc meta.structure.dictionary.jsonc meta.structure.dictionary.value.jsonc", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -199,7 +199,7 @@ }, { "c": "}", - "t": "source.json.jsonc meta.structure.dictionary.json.jsonc meta.structure.dictionary.value.json.jsonc meta.structure.dictionary.json.jsonc punctuation.definition.dictionary.end.json.jsonc", + "t": "source.jsonc meta.structure.dictionary.jsonc meta.structure.dictionary.value.jsonc meta.structure.dictionary.jsonc punctuation.definition.dictionary.end.jsonc", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -210,7 +210,7 @@ }, { "c": "}", - "t": "source.json.jsonc meta.structure.dictionary.json.jsonc punctuation.definition.dictionary.end.json.jsonc", + "t": "source.jsonc meta.structure.dictionary.jsonc punctuation.definition.dictionary.end.jsonc", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", From 90b1beeb5b962d160f88c3c34986ba84e5db0ef5 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 26 Jun 2018 10:15:04 -0700 Subject: [PATCH 085/228] Fix #52872 - settings editor breaks on search --- .../workbench/parts/preferences/browser/settingsEditor2.ts | 4 +++- src/vs/workbench/parts/preferences/browser/settingsTree.ts | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts b/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts index 8726d796de5..3e0bb79f096 100644 --- a/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts @@ -716,12 +716,14 @@ export class SettingsEditor2 extends BaseEditor { const [result] = results; if (!this.searchResultModel) { this.searchResultModel = this.instantiationService.createInstance(SearchResultModel, this.viewState); + this.searchResultModel.setResult(type, result); this.tocTreeModel.currentSearchModel = this.searchResultModel; this.toggleSearchMode(); this.settingsTree.setInput(this.searchResultModel); + } else { + this.searchResultModel.setResult(type, result); } - this.searchResultModel.setResult(type, result); this.tocTreeModel.update(); resolve(this.refreshTreeAndMaintainFocus()); }); diff --git a/src/vs/workbench/parts/preferences/browser/settingsTree.ts b/src/vs/workbench/parts/preferences/browser/settingsTree.ts index 6d82e55449d..eb577526abc 100644 --- a/src/vs/workbench/parts/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/parts/preferences/browser/settingsTree.ts @@ -901,6 +901,11 @@ export class SearchResultModel { setResult(type: SearchResultIdx, result: ISearchResult): void { this.cachedUniqueSearchResults = null; this.rawSearchResults = this.rawSearchResults || []; + if (!result) { + delete this.rawSearchResults[type]; + return; + } + this.rawSearchResults[type] = result; // Recompute children From 50cc3d6121a4673eac5a11a012ba5043dc4aae00 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 26 Jun 2018 11:07:37 -0700 Subject: [PATCH 086/228] Never edit files under `node_modules` on update paths Workaround for #52977 --- .../src/features/updatePathsOnRename.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/extensions/typescript-language-features/src/features/updatePathsOnRename.ts b/extensions/typescript-language-features/src/features/updatePathsOnRename.ts index e1c8ff89069..ac25ee879a5 100644 --- a/extensions/typescript-language-features/src/features/updatePathsOnRename.ts +++ b/extensions/typescript-language-features/src/features/updatePathsOnRename.ts @@ -226,11 +226,15 @@ export class UpdateImportsOnFileRenameHandler { const edits: Proto.FileCodeEdits[] = []; for (const edit of response.body) { // Workaround for https://github.com/Microsoft/vscode/issues/52675 + if ((edit as Proto.FileCodeEdits).fileName.match(/[\/\\]node_modules[\/\\]/gi)) { + continue; + } for (const change of (edit as Proto.FileCodeEdits).textChanges) { if (change.newText.match(/\/node_modules\//gi)) { continue; } } + edits.push(await this.fixEdit(edit, isDirectoryRename, oldFile, newFile)); } return typeConverters.WorkspaceEdit.fromFileCodeEdits(this.client, edits); From 2ed5aee8f09677be46d37b5ee64f4ac4fed26478 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 26 Jun 2018 11:38:46 -0700 Subject: [PATCH 087/228] Move webview serializer api out of proposed --- src/vs/vscode.d.ts | 57 +++++++++++++++++ src/vs/vscode.proposed.d.ts | 63 ------------------- src/vs/workbench/api/node/extHost.api.impl.ts | 6 +- 3 files changed, 60 insertions(+), 66 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 71329e06fea..b8746621f99 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -5539,6 +5539,50 @@ declare module 'vscode' { readonly webviewPanel: WebviewPanel; } + /** + * Restore webview panels that have been persisted when vscode shuts down. + * + * There are two types of webview persistence: + * + * - Persistence within a session. + * - Persistence across sessions (across restarts of VS Code). + * + * A `WebviewPanelSerializer` is only required for the second case: persisting a webview across sessions. + * + * Persistence within a session allows a webview to save its state when it becomes hidden + * and restore its content from this state when it becomes visible again. It is powered entirely + * by the webview content itself. To save off a persisted state, call `acquireVsCodeApi().setState()` with + * any json serializable object. To restore the state again, call `getState()` + * + * ```js + * // Within the webview + * const vscode = acquireVsCodeApi(); + * + * // Get existing state + * const oldState = vscode.getState() || { value: 0 }; + * + * // Update state + * setState({ value: oldState.value + 1 }) + * ``` + * + * A `WebviewPanelSerializer` extends this persistence across restarts of VS Code. When the editor is shutdown, VS Code will save off the state from `setState` of all webviews that have a serializer. When the + * webview first becomes visible after the restart, this state is passed to `deserializeWebviewPanel`. + * The extension can then restore the old `WebviewPanel` from this state. + */ + interface WebviewPanelSerializer { + /** + * Restore a webview panel from its seriailzed `state`. + * + * Called when a serialized webview first becomes visible. + * + * @param webviewPanel Webview panel to restore. The serializer should take ownership of this panel. + * @param state Persisted state. + * + * @return Thanble indicating that the webview has been fully restored. + */ + deserializeWebviewPanel(webviewPanel: WebviewPanel, state: any): Thenable; + } + /** * Namespace describing the environment the editor runs in. */ @@ -6145,6 +6189,19 @@ declare module 'vscode' { * @returns a [TreeView](#TreeView). */ export function createTreeView(viewId: string, options: { treeDataProvider: TreeDataProvider }): TreeView; + + /** + * Registers a webview panel serializer. + * + * Extensions that support reviving should have an `"onWebviewPanel:viewType"` activation method and + * make sure that [registerWebviewPanelSerializer](#registerWebviewPanelSerializer) is called during activation. + * + * Only a single serializer may be registered at a time for a given `viewType`. + * + * @param viewType Type of the webview panel that can be serialized. + * @param serializer Webview serializer. + */ + export function registerWebviewPanelSerializer(viewType: string, serializer: WebviewPanelSerializer): Disposable; } /** diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index e342923c195..bfc033ca751 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -857,69 +857,6 @@ declare module 'vscode' { } //#endregion - //#region Matt: WebView Serializer - - /** - * Restore webview panels that have been persisted when vscode shuts down. - * - * There are two types of webview persistence: - * - * - Persistence within a session. - * - Persistence across sessions (across restarts of VS Code). - * - * A `WebviewPanelSerializer` is only required for the second case: persisting a webview across sessions. - * - * Persistence within a session allows a webview to save its state when it becomes hidden - * and restore its content from this state when it becomes visible again. It is powered entirely - * by the webview content itself. To save off a persisted state, call `acquireVsCodeApi().setState()` with - * any json serializable object. To restore the state again, call `getState()` - * - * ```js - * // Within the webview - * const vscode = acquireVsCodeApi(); - * - * // Get existing state - * const oldState = vscode.getState() || { value: 0 }; - * - * // Update state - * setState({ value: oldState.value + 1 }) - * ``` - * - * A `WebviewPanelSerializer` extends this persistence across restarts of VS Code. When the editor is shutdown, VS Code will save off the state from `setState` of all webviews that have a serializer. When the - * webview first becomes visible after the restart, this state is passed to `deserializeWebviewPanel`. - * The extension can then restore the old `WebviewPanel` from this state. - */ - interface WebviewPanelSerializer { - /** - * Restore a webview panel from its seriailzed `state`. - * - * Called when a serialized webview first becomes visible. - * - * @param webviewPanel Webview panel to restore. The serializer should take ownership of this panel. - * @param state Persisted state. - * - * @return Thanble indicating that the webview has been fully restored. - */ - deserializeWebviewPanel(webviewPanel: WebviewPanel, state: any): Thenable; - } - - namespace window { - /** - * Registers a webview panel serializer. - * - * Extensions that support reviving should have an `"onWebviewPanel:viewType"` activation method and - * make sure that [registerWebviewPanelSerializer](#registerWebviewPanelSerializer) is called during activation. - * - * Only a single serializer may be registered at a time for a given `viewType`. - * - * @param viewType Type of the webview panel that can be serialized. - * @param serializer Webview serializer. - */ - export function registerWebviewPanelSerializer(viewType: string, serializer: WebviewPanelSerializer): Disposable; - } - - //#endregion - //#region Matt: Deinition range /** diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index 609db622a2b..15853b9ba76 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -442,6 +442,9 @@ export function createApiFactory( createTreeView(viewId: string, options: { treeDataProvider: vscode.TreeDataProvider }): vscode.TreeView { return extHostTreeViews.createTreeView(viewId, options); }, + registerWebviewPanelSerializer: (viewType: string, serializer: vscode.WebviewPanelSerializer) => { + return extHostWebviews.registerWebviewPanelSerializer(viewType, serializer); + }, // proposed API sampleFunction: proposedApiFunction(extension, () => { return extHostMessageService.showMessage(extension, Severity.Info, 'Hello Proposed Api!', {}, []); @@ -449,9 +452,6 @@ export function createApiFactory( registerDecorationProvider: proposedApiFunction(extension, (provider: vscode.DecorationProvider) => { return extHostDecorations.registerDecorationProvider(provider, extension.id); }), - registerWebviewPanelSerializer: proposedApiFunction(extension, (viewType: string, serializer: vscode.WebviewPanelSerializer) => { - return extHostWebviews.registerWebviewPanelSerializer(viewType, serializer); - }), registerProtocolHandler: proposedApiFunction(extension, (handler: vscode.ProtocolHandler) => { return extHostUrls.registerProtocolHandler(extension.id, handler); }), From 8237445753f9d0192056770f6918853e2a1696a6 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 26 Jun 2018 12:45:36 -0700 Subject: [PATCH 088/228] Rename cols to columns Fixes #52885 --- .../vscode-api-tests/src/singlefolder-tests/window.test.ts | 2 +- src/vs/vscode.proposed.d.ts | 2 +- src/vs/workbench/api/node/extHostTerminalService.ts | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts index 87734062b8a..50ef55b4f55 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts @@ -646,7 +646,7 @@ suite('window namespace tests', () => { }); const renderer = window.createTerminalRenderer('foo'); const reg2 = renderer.onDidChangeMaximumDimensions(dimensions => { - assert.ok(dimensions.cols > 0); + assert.ok(dimensions.columns > 0); assert.ok(dimensions.rows > 0); reg2.dispose(); const reg3 = window.onDidCloseTerminal(() => { diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index bfc033ca751..04d85d9ed84 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -351,7 +351,7 @@ declare module 'vscode' { /** * The number of columns in the terminal. */ - cols: number; + columns: number; /** * The number of rows in the terminal. diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index 5589c538331..e4f5405418c 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -179,7 +179,7 @@ export class ExtHostTerminalRenderer extends BaseExtHostTerminal implements vsco } return { rows: this._maximumDimensions.rows, - cols: this._maximumDimensions.cols + columns: this._maximumDimensions.columns }; } @@ -212,8 +212,8 @@ export class ExtHostTerminalRenderer extends BaseExtHostTerminal implements vsco this._onInput.fire(data); } - public _setMaximumDimensions(cols: number, rows: number): void { - this._maximumDimensions = { cols, rows }; + public _setMaximumDimensions(columns: number, rows: number): void { + this._maximumDimensions = { columns, rows }; this._onDidChangeMaximumDimensions.fire(this.maximumDimensions); } } From 6d600f0661548de9fb0f1430fbd6ffff7350f924 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 26 Jun 2018 12:50:41 -0700 Subject: [PATCH 089/228] Fix test compile --- .../vscode-api-tests/src/singlefolder-tests/window.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts index 50ef55b4f55..2a149aa2e1a 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts @@ -660,7 +660,7 @@ suite('window namespace tests', () => { test('TerminalRenderer.write should fire Terminal.onData', (done) => { const reg1 = window.onDidOpenTerminal(terminal => { reg1.dispose(); - const reg2 = terminal.onData(data => { + const reg2 = terminal.onDidWriteData(data => { assert.equal(data, 'bar'); reg2.dispose(); const reg3 = window.onDidCloseTerminal(() => { @@ -677,7 +677,7 @@ suite('window namespace tests', () => { test('Terminal.sendText should fire Termnial.onInput', (done) => { const reg1 = window.onDidOpenTerminal(terminal => { reg1.dispose(); - const reg2 = renderer.onInput(data => { + const reg2 = renderer.onDidAcceptInput(data => { assert.equal(data, 'bar'); reg2.dispose(); const reg3 = window.onDidCloseTerminal(() => { From e38de7201d3c622d803c491aabd7a72fd9045857 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 26 Jun 2018 12:52:43 -0700 Subject: [PATCH 090/228] Use ReadonlyArray for window.terminals Fixes #52888 --- 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 737e1b91bdf..e7ec8fd71bf 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -462,7 +462,7 @@ declare module 'vscode' { * * @readonly */ - export let terminals: Terminal[]; + export let terminals: ReadonlyArray; /** * The currently active terminal or `undefined`. The active terminal is the one that From 3d688527f93a1077a23032c55a829f3f4078d0db Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 26 Jun 2018 11:18:10 -0700 Subject: [PATCH 091/228] Settings editor - fix editing settings during search --- .../workbench/parts/preferences/browser/settingsEditor2.ts | 4 ++++ src/vs/workbench/parts/preferences/browser/settingsTree.ts | 6 ++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts b/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts index 3e0bb79f096..1d24f2d8eea 100644 --- a/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts @@ -565,6 +565,10 @@ export class SettingsEditor2 extends BaseEditor { resolvedSettingsRoot.children.push(resolveExtensionsSettings(dividedGroups.extension || [])); + if (this.searchResultModel) { + this.searchResultModel.updateChildren(); + } + if (this.settingsTreeModel) { this.settingsTreeModel.update(resolvedSettingsRoot); } else { diff --git a/src/vs/workbench/parts/preferences/browser/settingsTree.ts b/src/vs/workbench/parts/preferences/browser/settingsTree.ts index eb577526abc..429b2628bc9 100644 --- a/src/vs/workbench/parts/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/parts/preferences/browser/settingsTree.ts @@ -907,10 +907,12 @@ export class SearchResultModel { } this.rawSearchResults[type] = result; + this.updateChildren(); + } - // Recompute children + updateChildren(): void { this.children = this.getFlatSettings() - .map(s => createSettingsTreeSettingElement(s, result, this._viewState.settingsTarget, this._configurationService)); + .map(s => createSettingsTreeSettingElement(s, this, this._viewState.settingsTarget, this._configurationService)); } private getFlatSettings(): ISetting[] { From 52a057493946c0c0b6f52fe5a92d998900a151cf Mon Sep 17 00:00:00 2001 From: Ramya Achutha Rao Date: Tue, 26 Jun 2018 13:23:43 -0700 Subject: [PATCH 092/228] Shorten msg Fixes #53037 --- .../parts/extensions/common/extensionsFileTemplate.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/parts/extensions/common/extensionsFileTemplate.ts b/src/vs/workbench/parts/extensions/common/extensionsFileTemplate.ts index 1147e42ff1b..7f719e46e5f 100644 --- a/src/vs/workbench/parts/extensions/common/extensionsFileTemplate.ts +++ b/src/vs/workbench/parts/extensions/common/extensionsFileTemplate.ts @@ -26,7 +26,7 @@ export const ExtensionsConfigurationSchema: IJSONSchema = { }, unwantedRecommendations: { type: 'array', - description: localize('app.extensions.json.unwantedRecommendations', "List of extensions that will be skipped from the recommendations that VS Code makes for the users of this workspace. These are extensions that you may consider to be irrelevant, redundant, or otherwise unwanted. The identifier of an extension is always '${publisher}.${name}'. For example: 'vscode.csharp'."), + description: localize('app.extensions.json.unwantedRecommendations', "List of extensions recommended by VS Code that should not be recommended for users of this workspace. The identifier of an extension is always '${publisher}.${name}'. For example: 'vscode.csharp'."), items: { type: 'string', pattern: EXTENSION_IDENTIFIER_PATTERN, @@ -45,7 +45,7 @@ export const ExtensionsConfigurationInitialContent: string = [ '\t"recommendations": [', '\t\t', '\t],', - '\t// List of extensions that will be skipped from the recommendations that VS Code makes for the users of this workspace. These are extensions that you may consider to be irrelevant, redundant, or otherwise unwanted.', + '\t// List of extensions recommended by VS Code that should not be recommended for users of this workspace.', '\t"unwantedRecommendations": [', '\t\t', '\t]', From f0829585e243889df0a90f012597ad0576b5f3d5 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 26 Jun 2018 13:41:31 -0700 Subject: [PATCH 093/228] Mark events readonly and use const --- src/vs/vscode.proposed.d.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index e7ec8fd71bf..b5e74968559 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -447,30 +447,26 @@ declare module 'vscode' { * terminalRenderer.terminal.then(t => t.sendText('Hello world')); * ``` */ - onDidAcceptInput: Event; + readonly onDidAcceptInput: Event; /** * An event which fires when the [maximum dimensions](#TerminalRenderer.maimumDimensions) of * the terminal renderer change. */ - onDidChangeMaximumDimensions: Event; + readonly onDidChangeMaximumDimensions: Event; } export namespace window { /** * The currently opened terminals or an empty array. - * - * @readonly */ - export let terminals: ReadonlyArray; + export const terminals: ReadonlyArray; /** * The currently active terminal or `undefined`. The active terminal is the one that * currently has focus or most recently had focus. - * - * @readonly */ - export let activeTerminal: Terminal | undefined; + export const activeTerminal: Terminal | undefined; /** * An [event](#Event) which fires when the [active terminal](#window.activeTerminal) From 4c78feb8bd192c7ea868c5f963a91fbb782712b2 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 26 Jun 2018 13:59:20 -0700 Subject: [PATCH 094/228] Make TerminalDimesions properties readonly Fixes #53054 --- 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 b5e74968559..3372e7e8e2c 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -351,12 +351,12 @@ declare module 'vscode' { /** * The number of columns in the terminal. */ - columns: number; + readonly columns: number; /** * The number of rows in the terminal. */ - rows: number; + readonly rows: number; } /** From a97d6244c3f493edc976f53f5a64cd01b4accbce Mon Sep 17 00:00:00 2001 From: Ramya Achutha Rao Date: Tue, 26 Jun 2018 14:18:26 -0700 Subject: [PATCH 095/228] this.fileService not available inside array.map --- .../parts/stats/node/workspaceStats.ts | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/parts/stats/node/workspaceStats.ts b/src/vs/workbench/parts/stats/node/workspaceStats.ts index fbe53164bd1..6f597299df8 100644 --- a/src/vs/workbench/parts/stats/node/workspaceStats.ts +++ b/src/vs/workbench/parts/stats/node/workspaceStats.ts @@ -314,29 +314,33 @@ export class WorkspaceStats implements IWorkbenchContribution { } if (nameSet.has('package.json')) { - await TPromise.join(folders.map(async workspaceUri => { + const promises = []; + for (let i = 0; i < folders.length; i++) { + const workspaceUri = folders[i]; const uri = workspaceUri.with({ path: `${workspaceUri.path !== '/' ? workspaceUri.path : ''}/package.json` }); - try { - const content = await this.fileService.resolveContent(uri, { acceptTextOnly: true }); - const packageJsonContents = JSON.parse(content.value); - if (packageJsonContents['dependencies']) { - for (let module of ModulesToLookFor) { - if ('react-native' === module) { - if (packageJsonContents['dependencies'][module]) { - tags['workspace.reactNative'] = true; - } - } else { - if (packageJsonContents['dependencies'][module]) { - tags['workspace.npm.' + module] = true; + promises.push(this.fileService.resolveContent(uri, { acceptTextOnly: true }).then(content => { + try { + const packageJsonContents = JSON.parse(content.value); + if (packageJsonContents['dependencies']) { + for (let module of ModulesToLookFor) { + if ('react-native' === module) { + if (packageJsonContents['dependencies'][module]) { + tags['workspace.reactNative'] = true; + } + } else { + if (packageJsonContents['dependencies'][module]) { + tags['workspace.npm.' + module] = true; + } } } } } - } - catch (e) { - // Ignore errors when resolving file or parsing file contents - } - })); + catch (e) { + // Ignore errors when resolving file or parsing file contents + } + })); + } + await TPromise.join(promises); } } return TPromise.as(tags); From 34ba2e2fbfd196e2d6db5a4db0e42d03a97c655e Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 26 Jun 2018 16:23:24 -0700 Subject: [PATCH 096/228] Don't use questions for webview property docs Fixes #52949 --- 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 b8746621f99..ab02ccebbbe 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -5484,12 +5484,12 @@ declare module 'vscode' { readonly viewColumn?: ViewColumn; /** - * Is the panel currently active? + * Whether the panel is active (focused by the user). */ readonly active: boolean; /** - * Is the panel currently visible? + * Whether the panel is visible. */ readonly visible: boolean; From a5d4d74d0d38d05f26048e12e2da2fcec91fc7c2 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 26 Jun 2018 17:00:41 -0700 Subject: [PATCH 097/228] Fix #52887 - aggressive keybinding takes over enter key in settings.json --- .../parts/preferences/browser/settingsEditor2.ts | 12 +++++++++++- .../parts/preferences/common/preferences.ts | 1 + .../electron-browser/preferences.contribution.ts | 11 ++--------- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts b/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts index 1d24f2d8eea..f79f3b55dc3 100644 --- a/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts @@ -33,7 +33,7 @@ import { SearchWidget, SettingsTarget, SettingsTargetsWidget } from 'vs/workbenc import { commonlyUsedData, tocData } from 'vs/workbench/parts/preferences/browser/settingsLayout'; import { ISettingsEditorViewState, NonExpandableTree, resolveExtensionsSettings, resolveSettingsTree, SearchResultIdx, SearchResultModel, SettingsAccessibilityProvider, SettingsDataSource, SettingsRenderer, SettingsTreeController, SettingsTreeElement, SettingsTreeFilter, SettingsTreeGroupElement, SettingsTreeModel, SettingsTreeSettingElement } from 'vs/workbench/parts/preferences/browser/settingsTree'; import { TOCDataSource, TOCRenderer, TOCTreeModel } from 'vs/workbench/parts/preferences/browser/tocTree'; -import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_FIRST_ROW_FOCUS, CONTEXT_SETTINGS_SEARCH_FOCUS, IPreferencesSearchService, ISearchProvider } from 'vs/workbench/parts/preferences/common/preferences'; +import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_FIRST_ROW_FOCUS, CONTEXT_SETTINGS_SEARCH_FOCUS, IPreferencesSearchService, ISearchProvider, CONTEXT_SETTINGS_ROW_FOCUS } from 'vs/workbench/parts/preferences/common/preferences'; import { IPreferencesService, ISearchResult, ISettingsEditorModel } from 'vs/workbench/services/preferences/common/preferences'; import { SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput'; import { DefaultSettingsEditorModel } from 'vs/workbench/services/preferences/common/preferencesModels'; @@ -76,6 +76,7 @@ export class SettingsEditor2 extends BaseEditor { private searchResultModel: SearchResultModel; private firstRowFocused: IContextKey; + private rowFocused: IContextKey; private inSettingsEditorContextKey: IContextKey; private searchFocusContextKey: IContextKey; @@ -101,6 +102,7 @@ export class SettingsEditor2 extends BaseEditor { this.inSettingsEditorContextKey = CONTEXT_SETTINGS_EDITOR.bindTo(contextKeyService); this.searchFocusContextKey = CONTEXT_SETTINGS_SEARCH_FOCUS.bindTo(contextKeyService); this.firstRowFocused = CONTEXT_SETTINGS_FIRST_ROW_FOCUS.bindTo(contextKeyService); + this.rowFocused = CONTEXT_SETTINGS_ROW_FOCUS.bindTo(contextKeyService); this._register(configurationService.onDidChangeConfiguration(e => { this.onConfigUpdate(); @@ -359,12 +361,19 @@ export class SettingsEditor2 extends BaseEditor { this.selectedElement = e.focus; })); + this._register(this.settingsTree.onDidBlur(() => { + this.rowFocused.set(false); + this.firstRowFocused.set(false); + })); + this._register(this.settingsTree.onDidChangeSelection(e => { this.updateTreeScrollSync(); let firstRowFocused = false; + let rowFocused = false; const selection: SettingsTreeElement = e.selection[0]; if (selection) { + rowFocused = true; if (this.searchResultModel) { firstRowFocused = selection.id === this.searchResultModel.getChildren()[0].id; } else { @@ -373,6 +382,7 @@ export class SettingsEditor2 extends BaseEditor { } } + this.rowFocused.set(rowFocused); this.firstRowFocused.set(firstRowFocused); })); diff --git a/src/vs/workbench/parts/preferences/common/preferences.ts b/src/vs/workbench/parts/preferences/common/preferences.ts index 62c8bada9a7..ba8b28c4b26 100644 --- a/src/vs/workbench/parts/preferences/common/preferences.ts +++ b/src/vs/workbench/parts/preferences/common/preferences.ts @@ -61,6 +61,7 @@ export interface IKeybindingsEditor extends IEditor { export const CONTEXT_SETTINGS_EDITOR = new RawContextKey('inSettingsEditor', false); export const CONTEXT_SETTINGS_SEARCH_FOCUS = new RawContextKey('inSettingsSearch', false); export const CONTEXT_SETTINGS_FIRST_ROW_FOCUS = new RawContextKey('firstSettingRowFocused', false); +export const CONTEXT_SETTINGS_ROW_FOCUS = new RawContextKey('settingRowFocused', false); export const CONTEXT_KEYBINDINGS_EDITOR = new RawContextKey('inKeybindings', false); export const CONTEXT_KEYBINDINGS_SEARCH_FOCUS = new RawContextKey('inKeybindingsSearch', false); export const CONTEXT_KEYBINDING_FOCUS = new RawContextKey('keybindingFocus', false); diff --git a/src/vs/workbench/parts/preferences/electron-browser/preferences.contribution.ts b/src/vs/workbench/parts/preferences/electron-browser/preferences.contribution.ts index 367346b7259..a6452763887 100644 --- a/src/vs/workbench/parts/preferences/electron-browser/preferences.contribution.ts +++ b/src/vs/workbench/parts/preferences/electron-browser/preferences.contribution.ts @@ -22,7 +22,7 @@ import { KeybindingsEditor } from 'vs/workbench/parts/preferences/browser/keybin import { OpenRawDefaultSettingsAction, OpenSettingsAction, OpenGlobalSettingsAction, OpenGlobalKeybindingsFileAction, OpenWorkspaceSettingsAction, OpenFolderSettingsAction, ConfigureLanguageBasedSettingsAction, OPEN_FOLDER_SETTINGS_COMMAND, OpenGlobalKeybindingsAction, OpenSettings2Action } from 'vs/workbench/parts/preferences/browser/preferencesActions'; import { IKeybindingsEditor, IPreferencesSearchService, CONTEXT_KEYBINDING_FOCUS, CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS, KEYBINDINGS_EDITOR_COMMAND_DEFINE, KEYBINDINGS_EDITOR_COMMAND_REMOVE, KEYBINDINGS_EDITOR_COMMAND_SEARCH, - KEYBINDINGS_EDITOR_COMMAND_COPY, KEYBINDINGS_EDITOR_COMMAND_RESET, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND, KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR, KEYBINDINGS_EDITOR_COMMAND_FOCUS_KEYBINDINGS, KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_SEARCH, CONTEXT_SETTINGS_EDITOR, SETTINGS_EDITOR_COMMAND_FOCUS_FILE, CONTEXT_SETTINGS_SEARCH_FOCUS, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_FOCUS_NEXT_SETTING, SETTINGS_EDITOR_COMMAND_FOCUS_PREVIOUS_SETTING, SETTINGS_EDITOR_COMMAND_EDIT_FOCUSED_SETTING, SETTINGS_EDITOR_COMMAND_FOCUS_SEARCH_FROM_SETTINGS, SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_FROM_SEARCH, CONTEXT_SETTINGS_FIRST_ROW_FOCUS + KEYBINDINGS_EDITOR_COMMAND_COPY, KEYBINDINGS_EDITOR_COMMAND_RESET, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND, KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR, KEYBINDINGS_EDITOR_COMMAND_FOCUS_KEYBINDINGS, KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_SEARCH, CONTEXT_SETTINGS_EDITOR, SETTINGS_EDITOR_COMMAND_FOCUS_FILE, CONTEXT_SETTINGS_SEARCH_FOCUS, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_FOCUS_NEXT_SETTING, SETTINGS_EDITOR_COMMAND_FOCUS_PREVIOUS_SETTING, SETTINGS_EDITOR_COMMAND_EDIT_FOCUSED_SETTING, SETTINGS_EDITOR_COMMAND_FOCUS_SEARCH_FROM_SETTINGS, SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_FROM_SEARCH, CONTEXT_SETTINGS_FIRST_ROW_FOCUS, CONTEXT_SETTINGS_ROW_FOCUS } from 'vs/workbench/parts/preferences/common/preferences'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; @@ -346,7 +346,6 @@ class StartSearchDefaultSettingsCommand extends SettingsCommand { preferencesEditor.focusSearch(); } } - } const startSearchCommand = new StartSearchDefaultSettingsCommand({ id: SETTINGS_EDITOR_COMMAND_SEARCH, @@ -363,7 +362,6 @@ class FocusSearchFromSettingsCommand extends SettingsCommand { preferencesEditor.focusSearch(); } } - } const focusSearchFromSettingsCommand = new FocusSearchFromSettingsCommand({ id: SETTINGS_EDITOR_COMMAND_FOCUS_SEARCH_FROM_SETTINGS, @@ -381,7 +379,6 @@ class ClearSearchResultsCommand extends SettingsCommand { preferencesEditor.clearSearchResults(); } } - } const clearSearchResultsCommand = new ClearSearchResultsCommand({ id: SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, @@ -423,7 +420,6 @@ class FocusNextSearchResultCommand extends SettingsCommand { preferencesEditor.focusNextResult(); } } - } const focusNextSearchResultCommand = new FocusNextSearchResultCommand({ id: SETTINGS_EDITOR_COMMAND_FOCUS_NEXT_SETTING, @@ -440,7 +436,6 @@ class FocusPreviousSearchResultCommand extends SettingsCommand { preferencesEditor.focusPreviousResult(); } } - } const focusPreviousSearchResultCommand = new FocusPreviousSearchResultCommand({ id: SETTINGS_EDITOR_COMMAND_FOCUS_PREVIOUS_SETTING, @@ -457,7 +452,6 @@ class EditFocusedSettingCommand extends SettingsCommand { preferencesEditor.editFocusedPreference(); } } - } const editFocusedSettingCommand = new EditFocusedSettingCommand({ id: SETTINGS_EDITOR_COMMAND_EDIT_FOCUSED_SETTING, @@ -474,12 +468,11 @@ class EditFocusedSettingCommand2 extends SettingsCommand { preferencesEditor.editSelectedSetting(); } } - } const editFocusedSettingCommand2 = new EditFocusedSettingCommand2({ id: SETTINGS_EDITOR_COMMAND_EDIT_FOCUSED_SETTING, - precondition: CONTEXT_SETTINGS_EDITOR, + precondition: ContextKeyExpr.and(CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_ROW_FOCUS), kbOpts: { primary: KeyCode.Enter } }); KeybindingsRegistry.registerCommandAndKeybindingRule(editFocusedSettingCommand2.toCommandAndKeybindingRule(KeybindingsRegistry.WEIGHT.workbenchContrib())); From 33446a0a9e66efbfc106ffa08b948b63d77afdc2 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 26 Jun 2018 17:31:27 -0700 Subject: [PATCH 098/228] Use hierarchical markdown document symbols Fixes #52546 --- .../src/features/documentSymbolProvider.ts | 62 +++++++++++-- .../src/features/workspaceSymbolProvider.ts | 2 +- .../src/test/documentSymbolProvider.test.ts | 86 +++++++++++++++++++ 3 files changed, 144 insertions(+), 6 deletions(-) create mode 100644 extensions/markdown-language-features/src/test/documentSymbolProvider.test.ts diff --git a/extensions/markdown-language-features/src/features/documentSymbolProvider.ts b/extensions/markdown-language-features/src/features/documentSymbolProvider.ts index 38b33197200..bf0d5edb852 100644 --- a/extensions/markdown-language-features/src/features/documentSymbolProvider.ts +++ b/extensions/markdown-language-features/src/features/documentSymbolProvider.ts @@ -5,8 +5,13 @@ import * as vscode from 'vscode'; import { MarkdownEngine } from '../markdownEngine'; -import { TableOfContentsProvider, SkinnyTextDocument } from '../tableOfContentsProvider'; +import { TableOfContentsProvider, SkinnyTextDocument, TocEntry } from '../tableOfContentsProvider'; +interface MarkdownSymbol { + readonly level: number; + readonly parent: MarkdownSymbol | undefined; + readonly children: vscode.DocumentSymbol[]; +} export default class MDDocumentSymbolProvider implements vscode.DocumentSymbolProvider { @@ -14,10 +19,57 @@ export default class MDDocumentSymbolProvider implements vscode.DocumentSymbolPr private readonly engine: MarkdownEngine ) { } - public async provideDocumentSymbols(document: SkinnyTextDocument): Promise { + public async provideDocumentSymbolInformation(document: SkinnyTextDocument): Promise { const toc = await new TableOfContentsProvider(this.engine, document).getToc(); - return toc.map(entry => { - return new vscode.SymbolInformation('#'.repeat(entry.level) + ' ' + entry.text, vscode.SymbolKind.String, '', entry.location); - }); + return toc.map(entry => this.toSymbolInformation(entry)); + } + + public async provideDocumentSymbols(document: SkinnyTextDocument): Promise { + const toc = await new TableOfContentsProvider(this.engine, document).getToc(); + const root: MarkdownSymbol = { + level: -Infinity, + children: [], + parent: undefined + }; + this.buildTree(root, toc); + return root.children; + } + + private buildTree(parent: MarkdownSymbol, entries: TocEntry[]) { + if (!entries.length) { + return; + } + + const entry = entries[0]; + const symbol = this.toDocumentSymbol(entry); + symbol.children = []; + + while (parent && entry.level <= parent.level) { + parent = parent.parent!; + } + parent.children.push(symbol); + this.buildTree({ level: entry.level, children: symbol.children, parent }, entries.slice(1)); + } + + + private toSymbolInformation(entry: TocEntry): vscode.SymbolInformation { + return new vscode.SymbolInformation( + this.getSymbolName(entry), + vscode.SymbolKind.String, + '', + entry.location); + } + + private toDocumentSymbol(entry: TocEntry) { + return new vscode.DocumentSymbol( + this.getSymbolName(entry), + '', + vscode.SymbolKind.String, + entry.location.range, + entry.location.range); + } + + private getSymbolName(entry: TocEntry): string { + return '#'.repeat(entry.level) + ' ' + entry.text; } } \ No newline at end of file diff --git a/extensions/markdown-language-features/src/features/workspaceSymbolProvider.ts b/extensions/markdown-language-features/src/features/workspaceSymbolProvider.ts index 9cbce783e12..12e51c75d76 100644 --- a/extensions/markdown-language-features/src/features/workspaceSymbolProvider.ts +++ b/extensions/markdown-language-features/src/features/workspaceSymbolProvider.ts @@ -136,7 +136,7 @@ export default class MarkdownWorkspaceSymbolProvider implements vscode.Workspace private getSymbols(document: SkinnyTextDocument): Lazy> { return lazy(async () => { - return this._symbolProvider.provideDocumentSymbols(document); + return this._symbolProvider.provideDocumentSymbolInformation(document); }); } diff --git a/extensions/markdown-language-features/src/test/documentSymbolProvider.test.ts b/extensions/markdown-language-features/src/test/documentSymbolProvider.test.ts new file mode 100644 index 00000000000..b87b5cf2ea4 --- /dev/null +++ b/extensions/markdown-language-features/src/test/documentSymbolProvider.test.ts @@ -0,0 +1,86 @@ +/*--------------------------------------------------------------------------------------------- + * 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 'mocha'; +import * as vscode from 'vscode'; +import SymbolProvider from '../features/documentSymbolProvider'; +import { InMemoryDocument } from './inMemoryDocument'; +import { createNewMarkdownEngine } from './engine'; + + +const testFileName = vscode.Uri.parse('test.md'); + + +function getSymbolsForFile(fileContents: string) { + const doc = new InMemoryDocument(testFileName, fileContents); + const provider = new SymbolProvider(createNewMarkdownEngine()); + return provider.provideDocumentSymbols(doc); +} + + +suite('markdown.DocumentSymbolProvider', () => { + test('Should not return anything for empty document', async () => { + const symbols = await getSymbolsForFile(''); + assert.strictEqual(symbols.length, 0); + }); + + test('Should not return anything for document with no headers', async () => { + const symbols = await getSymbolsForFile('a\na'); + assert.strictEqual(symbols.length, 0); + }); + + test('Should not return anything for document with # but no real headers', async () => { + const symbols = await getSymbolsForFile('a#a\na#'); + assert.strictEqual(symbols.length, 0); + }); + + test('Should return single symbol for single header', async () => { + const symbols = await getSymbolsForFile('# h'); + assert.strictEqual(symbols.length, 1); + assert.strictEqual(symbols[0].name, '# h'); + }); + + test('Should not care about symbol level for single header', async () => { + const symbols = await getSymbolsForFile('### h'); + assert.strictEqual(symbols.length, 1); + assert.strictEqual(symbols[0].name, '### h'); + }); + + test('Should put symbols of same level in flat list', async () => { + const symbols = await getSymbolsForFile('## h\n## h2'); + assert.strictEqual(symbols.length, 2); + assert.strictEqual(symbols[0].name, '## h'); + assert.strictEqual(symbols[1].name, '## h2'); + }); + + test('Should nest symbol of level - 1 under parent', async () => { + + const symbols = await getSymbolsForFile('# h\n## h2\n## h3'); + assert.strictEqual(symbols.length, 1); + assert.strictEqual(symbols[0].name, '# h'); + assert.strictEqual(symbols[0].children.length, 2); + assert.strictEqual(symbols[0].children[0].name, '## h2'); + assert.strictEqual(symbols[0].children[1].name, '## h3'); + }); + + test('Should nest symbol of level - n under parent', async () => { + const symbols = await getSymbolsForFile('# h\n#### h2'); + assert.strictEqual(symbols.length, 1); + assert.strictEqual(symbols[0].name, '# h'); + assert.strictEqual(symbols[0].children.length, 1); + assert.strictEqual(symbols[0].children[0].name, '#### h2'); + }); + + test('Should flatten children where lower level occurs first', async () => { + const symbols = await getSymbolsForFile('# h\n### h2\n## h3'); + assert.strictEqual(symbols.length, 1); + assert.strictEqual(symbols[0].name, '# h'); + assert.strictEqual(symbols[0].children.length, 2); + assert.strictEqual(symbols[0].children[0].name, '### h2'); + assert.strictEqual(symbols[0].children[1].name, '## h3'); + }); +}); + From b63be4770e4357ef06c262f2d03f96b9ac206d7d Mon Sep 17 00:00:00 2001 From: Greg Van Liew Date: Tue, 26 Jun 2018 17:41:38 -0700 Subject: [PATCH 099/228] Use VS Code with a space in comments --- .../localizations/electron-browser/localizationsActions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/parts/localizations/electron-browser/localizationsActions.ts b/src/vs/workbench/parts/localizations/electron-browser/localizationsActions.ts index 71276cedb68..d74ddb90984 100644 --- a/src/vs/workbench/parts/localizations/electron-browser/localizationsActions.ts +++ b/src/vs/workbench/parts/localizations/electron-browser/localizationsActions.ts @@ -22,10 +22,10 @@ export class ConfigureLocaleAction extends Action { private static DEFAULT_CONTENT: string = [ '{', - `\t// ${localize('displayLanguage', 'Defines VSCode\'s display language.')}`, + `\t// ${localize('displayLanguage', 'Defines VS Code\'s display language.')}`, `\t// ${localize('doc', 'See {0} for a list of supported languages.', 'https://go.microsoft.com/fwlink/?LinkId=761051')}`, `\t`, - `\t"locale":"${language}" // ${localize('restart', 'Changes will not take effect until VSCode has been restarted.')}`, + `\t"locale":"${language}" // ${localize('restart', 'Changes will not take effect until VS Code has been restarted.')}`, '}' ].join('\n'); From 53516c2ef97f4f628acdfa7423d913faa3c3294d Mon Sep 17 00:00:00 2001 From: Greg Van Liew Date: Tue, 26 Jun 2018 17:59:07 -0700 Subject: [PATCH 100/228] Use VS Code with a space in comments (#53078) --- .../localizations/electron-browser/localizationsActions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/parts/localizations/electron-browser/localizationsActions.ts b/src/vs/workbench/parts/localizations/electron-browser/localizationsActions.ts index 71276cedb68..d74ddb90984 100644 --- a/src/vs/workbench/parts/localizations/electron-browser/localizationsActions.ts +++ b/src/vs/workbench/parts/localizations/electron-browser/localizationsActions.ts @@ -22,10 +22,10 @@ export class ConfigureLocaleAction extends Action { private static DEFAULT_CONTENT: string = [ '{', - `\t// ${localize('displayLanguage', 'Defines VSCode\'s display language.')}`, + `\t// ${localize('displayLanguage', 'Defines VS Code\'s display language.')}`, `\t// ${localize('doc', 'See {0} for a list of supported languages.', 'https://go.microsoft.com/fwlink/?LinkId=761051')}`, `\t`, - `\t"locale":"${language}" // ${localize('restart', 'Changes will not take effect until VSCode has been restarted.')}`, + `\t"locale":"${language}" // ${localize('restart', 'Changes will not take effect until VS Code has been restarted.')}`, '}' ].join('\n'); From e8a9ee5bc9332e66d7c072fbfcaf510da0a69693 Mon Sep 17 00:00:00 2001 From: Greg Van Liew Date: Tue, 26 Jun 2018 18:01:17 -0700 Subject: [PATCH 101/228] User VS Code with a space in built-in extension descriptions. --- extensions/grunt/package.json | 4 ++-- extensions/grunt/package.nls.json | 4 ++-- extensions/jake/package.nls.json | 4 ++-- extensions/npm/package.nls.json | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/extensions/grunt/package.json b/extensions/grunt/package.json index 182fe384325..cfc88d5dea3 100644 --- a/extensions/grunt/package.json +++ b/extensions/grunt/package.json @@ -1,8 +1,8 @@ { "name": "grunt", "publisher": "vscode", - "description": "Extension to add Grunt capabilities to VSCode.", - "displayName": "Grunt support for VSCode", + "description": "Extension to add Grunt capabilities to VS Code.", + "displayName": "Grunt support for VS Code", "version": "1.0.0", "icon": "images/grunt.png", "engines": { diff --git a/extensions/grunt/package.nls.json b/extensions/grunt/package.nls.json index 31f3b369af5..7f1b68bbe0b 100644 --- a/extensions/grunt/package.nls.json +++ b/extensions/grunt/package.nls.json @@ -1,6 +1,6 @@ { - "description": "Extension to add Grunt capabilities to VSCode.", - "displayName": "Grunt support for VSCode", + "description": "Extension to add Grunt capabilities to VS Code.", + "displayName": "Grunt support for VS Code", "config.grunt.autoDetect": "Controls whether auto detection of Grunt tasks is on or off. Default is on.", "grunt.taskDefinition.type.description": "The Grunt task to customize.", "grunt.taskDefinition.file.description": "The Grunt file that provides the task. Can be omitted." diff --git a/extensions/jake/package.nls.json b/extensions/jake/package.nls.json index b8cdb170d33..8150f471e6d 100644 --- a/extensions/jake/package.nls.json +++ b/extensions/jake/package.nls.json @@ -1,6 +1,6 @@ { - "description": "Extension to add Jake capabilities to VSCode.", - "displayName": "Jake support for VSCode", + "description": "Extension to add Jake capabilities to VS Code.", + "displayName": "Jake support for VS Code", "jake.taskDefinition.type.description": "The Jake task to customize.", "jake.taskDefinition.file.description": "The Jake file that provides the task. Can be omitted.", "config.jake.autoDetect": "Controls whether auto detection of Jake tasks is on or off. Default is on." diff --git a/extensions/npm/package.nls.json b/extensions/npm/package.nls.json index ccc6aa3114c..92665d5f65a 100644 --- a/extensions/npm/package.nls.json +++ b/extensions/npm/package.nls.json @@ -1,6 +1,6 @@ { "description": "Extension to add task support for npm scripts.", - "displayName": "Npm support for VSCode", + "displayName": "Npm support for VS Code", "config.npm.autoDetect": "Controls whether auto detection of npm scripts is on or off. Default is on.", "config.npm.runSilent": "Run npm commands with the `--silent` option.", "config.npm.packageManager": "The package manager used to run scripts.", From 14274ef7d16c96cecce0801ed0778319fb046cc6 Mon Sep 17 00:00:00 2001 From: Greg Van Liew Date: Tue, 26 Jun 2018 18:38:18 -0700 Subject: [PATCH 102/228] Use VS Code with a space in built-in extension descriptions. (#53080) * Use VS Code with a space in comments * User VS Code with a space in built-in extension descriptions. --- extensions/grunt/package.json | 4 ++-- extensions/grunt/package.nls.json | 4 ++-- extensions/jake/package.nls.json | 4 ++-- extensions/npm/package.nls.json | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/extensions/grunt/package.json b/extensions/grunt/package.json index 182fe384325..cfc88d5dea3 100644 --- a/extensions/grunt/package.json +++ b/extensions/grunt/package.json @@ -1,8 +1,8 @@ { "name": "grunt", "publisher": "vscode", - "description": "Extension to add Grunt capabilities to VSCode.", - "displayName": "Grunt support for VSCode", + "description": "Extension to add Grunt capabilities to VS Code.", + "displayName": "Grunt support for VS Code", "version": "1.0.0", "icon": "images/grunt.png", "engines": { diff --git a/extensions/grunt/package.nls.json b/extensions/grunt/package.nls.json index 31f3b369af5..7f1b68bbe0b 100644 --- a/extensions/grunt/package.nls.json +++ b/extensions/grunt/package.nls.json @@ -1,6 +1,6 @@ { - "description": "Extension to add Grunt capabilities to VSCode.", - "displayName": "Grunt support for VSCode", + "description": "Extension to add Grunt capabilities to VS Code.", + "displayName": "Grunt support for VS Code", "config.grunt.autoDetect": "Controls whether auto detection of Grunt tasks is on or off. Default is on.", "grunt.taskDefinition.type.description": "The Grunt task to customize.", "grunt.taskDefinition.file.description": "The Grunt file that provides the task. Can be omitted." diff --git a/extensions/jake/package.nls.json b/extensions/jake/package.nls.json index b8cdb170d33..8150f471e6d 100644 --- a/extensions/jake/package.nls.json +++ b/extensions/jake/package.nls.json @@ -1,6 +1,6 @@ { - "description": "Extension to add Jake capabilities to VSCode.", - "displayName": "Jake support for VSCode", + "description": "Extension to add Jake capabilities to VS Code.", + "displayName": "Jake support for VS Code", "jake.taskDefinition.type.description": "The Jake task to customize.", "jake.taskDefinition.file.description": "The Jake file that provides the task. Can be omitted.", "config.jake.autoDetect": "Controls whether auto detection of Jake tasks is on or off. Default is on." diff --git a/extensions/npm/package.nls.json b/extensions/npm/package.nls.json index ccc6aa3114c..92665d5f65a 100644 --- a/extensions/npm/package.nls.json +++ b/extensions/npm/package.nls.json @@ -1,6 +1,6 @@ { "description": "Extension to add task support for npm scripts.", - "displayName": "Npm support for VSCode", + "displayName": "Npm support for VS Code", "config.npm.autoDetect": "Controls whether auto detection of npm scripts is on or off. Default is on.", "config.npm.runSilent": "Run npm commands with the `--silent` option.", "config.npm.packageManager": "The package manager used to run scripts.", From 632c0910b54d06b19762cc66d16cb2b015ffa127 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 26 Jun 2018 18:32:45 -0700 Subject: [PATCH 103/228] Fix #53084 --- .../parts/preferences/browser/media/settingsEditor2.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css index f6567390f89..89a5a29cc60 100644 --- a/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css +++ b/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css @@ -134,6 +134,7 @@ .settings-editor > .settings-body .settings-tree-container { flex: 1; + margin-right: 1px; /* So the item doesn't blend into the edge of the view container */ border-spacing: 0; border-collapse: separate; position: relative; From 186d148cce9250dfa8d0459ee0b6ac9feba65a16 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 26 Jun 2018 18:43:38 -0700 Subject: [PATCH 104/228] Fix #53083 - add minimum width for TOC in settings editor --- .../parts/preferences/browser/media/settingsEditor2.css | 6 +++++- .../workbench/parts/preferences/browser/settingsEditor2.ts | 6 ++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css index 89a5a29cc60..5c553f38f89 100644 --- a/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css +++ b/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css @@ -118,7 +118,11 @@ display: none; } -.search-mode .settings-toc-container { +.settings-editor.search-mode .settings-toc-container { + display: none; +} + +.settings-editor.narrow > .settings-body .settings-toc-container { display: none; } diff --git a/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts b/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts index f79f3b55dc3..f973c023821 100644 --- a/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts @@ -135,7 +135,9 @@ export class SettingsEditor2 extends BaseEditor { layout(dimension: DOM.Dimension): void { this.searchWidget.layout(dimension); - this.layoutSettingsList(dimension); + this.layoutTrees(dimension); + + DOM.toggleClass(this.rootElement, 'narrow', dimension.width < 600); } focus(): void { @@ -770,7 +772,7 @@ export class SettingsEditor2 extends BaseEditor { }); } - private layoutSettingsList(dimension: DOM.Dimension): void { + private layoutTrees(dimension: DOM.Dimension): void { const listHeight = dimension.height - (DOM.getDomNodePagePosition(this.headerContainer).height + 12 /*padding*/); this.settingsTreeContainer.style.height = `${listHeight}px`; this.settingsTree.layout(listHeight, 800); From 2a95e35287dfc2d25d8346ae913a38b28933e63f Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Tue, 26 Jun 2018 19:11:21 -0700 Subject: [PATCH 105/228] Add recommendations back to side bad after restore (#53025) --- .../electron-browser/extensionTipsService.ts | 80 ++++++++++++------- 1 file changed, 53 insertions(+), 27 deletions(-) diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts index 7f4a45ba4be..37073609423 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts @@ -60,7 +60,7 @@ function caseInsensitiveGet(obj: { [key: string]: T }, key: string): T | unde return undefined; } for (const _key in obj) { - if (obj.hasOwnProperty(_key) && _key.toLowerCase() === key.toLowerCase()) { + if (Object.hasOwnProperty.call(obj, _key) && _key.toLowerCase() === key.toLowerCase()) { return obj[_key]; } } @@ -87,8 +87,8 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe private readonly _onRecommendationChange: Emitter = new Emitter(); onRecommendationChange: Event = this._onRecommendationChange.event; - private _sessionIgnoredRecommendations: { [id: string]: { reasonId: ExtensionRecommendationReason } } = {}; - private _sessionRestoredRecommendations: { [id: string]: { reasonId: ExtensionRecommendationReason } } = {}; + private _sessionIgnoredRecommendations: { [id: string]: { reasonId: ExtensionRecommendationReason, reasonText: string, sources: ExtensionRecommendationSource[] } } = {}; + private _sessionRestoredRecommendations: { [id: string]: { reasonId: ExtensionRecommendationReason, reasonText: string, sources: ExtensionRecommendationSource[] } } = {}; constructor( @IExtensionGalleryService private readonly _galleryService: IExtensionGalleryService, @@ -222,7 +222,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe Object.keys(this._sessionRestoredRecommendations).forEach(x => output[x.toLowerCase()] = { reasonId: this._sessionRestoredRecommendations[x].reasonId, - reasonText: localize('restoredRecommendation', "You will receive recommendations for this extension in your future VS Code sessions.") + reasonText: this._sessionRestoredRecommendations[x].reasonText }); return output; @@ -395,18 +395,24 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe } getFileBasedRecommendations(): IExtensionRecommendation[] { - return Object.keys(this._fileBasedRecommendations) - .sort((a, b) => { - if (this._fileBasedRecommendations[a].recommendedTime === this._fileBasedRecommendations[b].recommendedTime) { - if (!product.extensionImportantTips || caseInsensitiveGet(product.extensionImportantTips, a)) { - return -1; - } - if (caseInsensitiveGet(product.extensionImportantTips, b)) { - return 1; - } + return [...this.getRestoredRecommendationsByReason(ExtensionRecommendationReason.File), + ...Object.keys(this._fileBasedRecommendations).sort((a, b) => { + if (this._fileBasedRecommendations[a].recommendedTime === this._fileBasedRecommendations[b].recommendedTime) { + if (!product.extensionImportantTips || caseInsensitiveGet(product.extensionImportantTips, a)) { + return -1; } - return this._fileBasedRecommendations[a].recommendedTime > this._fileBasedRecommendations[b].recommendedTime ? -1 : 1; - }).map(extensionId => ({ extensionId, sources: this._fileBasedRecommendations[extensionId].sources })); + if (caseInsensitiveGet(product.extensionImportantTips, b)) { + return 1; + } + } + return this._fileBasedRecommendations[a].recommendedTime > this._fileBasedRecommendations[b].recommendedTime ? -1 : 1; + })] + .map(extensionId => ({ + extensionId, sources: + this._fileBasedRecommendations[extensionId] ? this._fileBasedRecommendations[extensionId].sources : + this._sessionRestoredRecommendations[extensionId.toLowerCase()] ? this._sessionRestoredRecommendations[extensionId.toLowerCase()].sources : + [] + })); } getOtherRecommendations(): TPromise { @@ -414,21 +420,34 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe const others = distinct([ ...Object.keys(this._exeBasedRecommendations), ...this._dynamicWorkspaceRecommendations, - ...Object.keys(this._experimentalRecommendations)]); + ...Object.keys(this._experimentalRecommendations), + ]); shuffle(others); - return others.map(extensionId => { - const sources: ExtensionRecommendationSource[] = []; - if (this._exeBasedRecommendations[extensionId]) { - sources.push('executable'); - } - if (this._dynamicWorkspaceRecommendations[extensionId]) { - sources.push('dynamic'); - } - return ({ extensionId, sources }); - }); + return [ + ...this.getRestoredRecommendationsByReason(ExtensionRecommendationReason.Executable), + ...this.getRestoredRecommendationsByReason(ExtensionRecommendationReason.DynamicWorkspace), + ...this.getRestoredRecommendationsByReason(ExtensionRecommendationReason.Experimental), + ...others] + .map(extensionId => { + const sources: ExtensionRecommendationSource[] = []; + if (this._exeBasedRecommendations[extensionId]) { + sources.push('executable'); + } + if (this._dynamicWorkspaceRecommendations[extensionId]) { + sources.push('dynamic'); + } + if (this._sessionRestoredRecommendations[extensionId.toLowerCase()]) { + sources.push(...this._sessionRestoredRecommendations[extensionId.toLowerCase()].sources); + } + return ({ extensionId, sources }); + }); }); } + private getRestoredRecommendationsByReason(reason: ExtensionRecommendationReason): string[] { + return Object.keys(this._sessionRestoredRecommendations).filter(key => this._sessionRestoredRecommendations[key].reasonId === reason); + } + getKeymapRecommendations(): IExtensionRecommendation[] { return (product.keymapExtensionTips || []).map(extensionId => ({ extensionId, sources: ['application'] })); } @@ -1000,7 +1019,14 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe */ this.telemetryService.publicLog('extensionsRecommendations:ignoreRecommendation', { id: extensionId, recommendationReason: reason.reasonId }); } - this._sessionIgnoredRecommendations[lowerId] = reason; + this._sessionIgnoredRecommendations[lowerId] = { + ...reason, sources: + coalesce([ + <'executable' | null>(caseInsensitiveGet(this._exeBasedRecommendations, lowerId) ? 'executable' : null), + ...(() => { let a = caseInsensitiveGet(this._fileBasedRecommendations, lowerId); return a ? a.sources : null; })(), + <'dynamic' | null>(this._dynamicWorkspaceRecommendations.filter(x => x.toLowerCase() === lowerId).length > 0 ? 'dynamic' : null), + ]) + }; delete this._sessionRestoredRecommendations[lowerId]; this._globallyIgnoredRecommendations = distinct([...this._globallyIgnoredRecommendations, lowerId].map(id => id.toLowerCase())); } else { From aac4d208bfbc0d4fa37453ae0afa680f9502dc3d Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Tue, 26 Jun 2018 19:48:28 -0700 Subject: [PATCH 106/228] fixes #52505 --- .../browser/parts/menubar/menubarPart.ts | 93 ++++++++++++------- 1 file changed, 59 insertions(+), 34 deletions(-) diff --git a/src/vs/workbench/browser/parts/menubar/menubarPart.ts b/src/vs/workbench/browser/parts/menubar/menubarPart.ts index 31a6686f187..c505adab959 100644 --- a/src/vs/workbench/browser/parts/menubar/menubarPart.ts +++ b/src/vs/workbench/browser/parts/menubar/menubarPart.ts @@ -86,6 +86,7 @@ export class MenubarPart extends Part { private actionRunner: IActionRunner; private container: Builder; + private _modifierKeyStatus: IModifierKeyStatus; private _isFocused: boolean; private _onVisibilityChange: Emitter; @@ -227,22 +228,25 @@ export class MenubarPart extends Part { this.container.style('visibility', null); } - private onAltKeyToggled(altKeyDown: boolean): void { + private onModifierKeyToggled(modiferKeyStatus: IModifierKeyStatus): void { if (this.currentMenubarVisibility === 'toggle') { - if (altKeyDown) { + const altKeyPressed = (!this._modifierKeyStatus || !this._modifierKeyStatus.altKey) && modiferKeyStatus.altKey; + if (altKeyPressed && !modiferKeyStatus.ctrlKey && !modiferKeyStatus.shiftKey) { this.showMenubar(); } else if (!this.isFocused) { this.hideMenubar(); } } + this._modifierKeyStatus = modiferKeyStatus; + if (this.currentEnableMenuBarMnemonics && this.customMenus) { this.customMenus.forEach(customMenu => { let child = customMenu.titleElement.child(); if (child) { let grandChild = child.child(); if (grandChild) { - grandChild.style('text-decoration', altKeyDown ? 'underline' : null); + grandChild.style('text-decoration', modiferKeyStatus.altKey ? 'underline' : null); } } }); @@ -261,7 +265,7 @@ export class MenubarPart extends Part { // Listen to keybindings change this.keybindingService.onDidUpdateKeybindings(() => this.setupMenubar()); - AlternativeKeyEmitter.getInstance().event(this.onAltKeyToggled, this); + ModifierKeyEmitter.getInstance().event(this.onModifierKeyToggled, this); } private setupMenubar(): void { @@ -398,6 +402,10 @@ export class MenubarPart extends Part { updateActions(menu, this.customMenus[menuIndex].actions); this.customMenus[menuIndex].titleElement.on(EventType.CLICK, (event) => { + if (this._modifierKeyStatus && (this._modifierKeyStatus.shiftKey || this._modifierKeyStatus.ctrlKey)) { + return; // supress keyboard shortcuts that shouldn't conflict + } + this.toggleCustomMenu(menuIndex); this.isFocused = !this.isFocused; }); @@ -684,65 +692,82 @@ export class MenubarPart extends Part { } } -class AlternativeKeyEmitter extends Emitter { +interface IModifierKeyStatus { + altKey: boolean; + shiftKey: boolean; + ctrlKey: boolean; +} + +class ModifierKeyEmitter extends Emitter { private _subscriptions: IDisposable[] = []; - private _isPressed: boolean; - private static instance: AlternativeKeyEmitter; - private _suppressAltKeyUp: boolean = false; + private _isPressed: IModifierKeyStatus; + private static instance: ModifierKeyEmitter; private constructor() { super(); + this._isPressed = { + altKey: false, + shiftKey: false, + ctrlKey: false + }; + this._subscriptions.push(domEvent(document.body, 'keydown')(e => { - if (e.altKey) { - this.isPressed = true; + if (e.altKey || e.shiftKey || e.ctrlKey) { + this.isPressed = { + altKey: e.altKey, + ctrlKey: e.ctrlKey, + shiftKey: e.shiftKey + }; } })); this._subscriptions.push(domEvent(document.body, 'keyup')(e => { - if (this.isPressed && !e.altKey) { - if (this._suppressAltKeyUp) { - e.preventDefault(); - } + if ((!e.altKey && this.isPressed.altKey) || + (!e.shiftKey && this.isPressed.shiftKey) || + (!e.ctrlKey && this.isPressed.ctrlKey) + ) { - this._suppressAltKeyUp = false; - this.isPressed = false; - } - })); - this._subscriptions.push(domEvent(document.body, 'mouseleave')(e => { - if (this.isPressed) { - this.isPressed = false; + this.isPressed = { + altKey: e.altKey, + shiftKey: e.shiftKey, + ctrlKey: e.ctrlKey + }; } })); this._subscriptions.push(domEvent(document.body, 'blur')(e => { - if (this.isPressed) { - this.isPressed = false; + if (this.isPressed.altKey || this.isPressed.shiftKey || this.isPressed.ctrlKey) { + this.isPressed = { + altKey: false, + shiftKey: false, + ctrlKey: false + }; } })); } - get isPressed(): boolean { + get isPressed(): IModifierKeyStatus { return this._isPressed; } - set isPressed(value: boolean) { + set isPressed(value: IModifierKeyStatus) { + if (this._isPressed.altKey === value.altKey && + this._isPressed.shiftKey === value.shiftKey && + this._isPressed.ctrlKey === value.ctrlKey) { + return; + } + this._isPressed = value; this.fire(this._isPressed); } - suppressAltKeyUp() { - // Sometimes the native alt behavior needs to be suppresed since the alt was already used as an alternative key - // Example: windows behavior to toggle tha top level menu #44396 - this._suppressAltKeyUp = true; - } - static getInstance() { - if (!AlternativeKeyEmitter.instance) { - AlternativeKeyEmitter.instance = new AlternativeKeyEmitter(); + if (!ModifierKeyEmitter.instance) { + ModifierKeyEmitter.instance = new ModifierKeyEmitter(); } - return AlternativeKeyEmitter.instance; + return ModifierKeyEmitter.instance; } dispose() { From f2fa6bea46bde9856e6a593e4d4c5af2b303ac66 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 26 Jun 2018 21:18:10 -0700 Subject: [PATCH 107/228] Fix #52829 --- src/vs/platform/search/common/search.ts | 2 +- .../api/electron-browser/mainThreadSearch.ts | 2 +- .../services/search/node/searchService.ts | 66 +++++++++++-------- 3 files changed, 40 insertions(+), 30 deletions(-) diff --git a/src/vs/platform/search/common/search.ts b/src/vs/platform/search/common/search.ts index ded634a58a4..2e1e6dcab87 100644 --- a/src/vs/platform/search/common/search.ts +++ b/src/vs/platform/search/common/search.ts @@ -27,7 +27,7 @@ export interface ISearchService { search(query: ISearchQuery): PPromise; extendQuery(query: ISearchQuery): void; clearCache(cacheKey: string): TPromise; - registerSearchResultProvider(provider: ISearchResultProvider): IDisposable; + registerSearchResultProvider(scheme: string, provider: ISearchResultProvider): IDisposable; } export interface ISearchHistoryValues { diff --git a/src/vs/workbench/api/electron-browser/mainThreadSearch.ts b/src/vs/workbench/api/electron-browser/mainThreadSearch.ts index 2fc636b81e1..6e692f064b7 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadSearch.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadSearch.ts @@ -86,7 +86,7 @@ class RemoteSearchProvider implements ISearchResultProvider { private readonly _handle: number, private readonly _proxy: ExtHostSearchShape ) { - this._registrations = [searchService.registerSearchResultProvider(this)]; + this._registrations = [searchService.registerSearchResultProvider(this._scheme, this)]; } dispose(): void { diff --git a/src/vs/workbench/services/search/node/searchService.ts b/src/vs/workbench/services/search/node/searchService.ts index f9bb3b703fe..e7159cee743 100644 --- a/src/vs/workbench/services/search/node/searchService.ts +++ b/src/vs/workbench/services/search/node/searchService.ts @@ -32,6 +32,7 @@ export class SearchService implements ISearchService { private diskSearch: DiskSearch; private readonly searchProviders: ISearchResultProvider[] = []; + private fileSearchProvider: ISearchResultProvider; private forwardingTelemetry: PPromise; constructor( @@ -46,13 +47,22 @@ export class SearchService implements ISearchService { this.diskSearch = new DiskSearch(!environmentService.isBuilt || environmentService.verbose, /*timeout=*/undefined, environmentService.debugSearch); } - public registerSearchResultProvider(provider: ISearchResultProvider): IDisposable { - this.searchProviders.push(provider); + public registerSearchResultProvider(scheme: string, provider: ISearchResultProvider): IDisposable { + if (scheme === 'file') { + this.fileSearchProvider = provider; + } else { + this.searchProviders.push(provider); + } + return { dispose: () => { - const idx = this.searchProviders.indexOf(provider); - if (idx >= 0) { - this.searchProviders.splice(idx, 1); + if (scheme === 'file') { + this.fileSearchProvider = null; + } else { + const idx = this.searchProviders.indexOf(provider); + if (idx >= 0) { + this.searchProviders.splice(idx, 1); + } } } }; @@ -115,32 +125,32 @@ export class SearchService implements ISearchService { }); const providerPromise = this.extensionService.whenInstalledExtensionsRegistered().then(() => { - // If no search providers are registered, fall back on DiskSearch // TODO@roblou this is not properly waiting for search-rg to finish registering itself - if (this.searchProviders.length) { - return TPromise.join(this.searchProviders.map(p => searchWithProvider(p))) - .then(completes => { - completes = completes.filter(c => !!c); - if (!completes.length) { - return null; - } + // If no search provider has been registered for the 'file' schema, fall back on DiskSearch + const providers = [ + this.fileSearchProvider || this.diskSearch, + ...this.searchProviders + ]; + return TPromise.join(providers.map(p => searchWithProvider(p))) + .then(completes => { + completes = completes.filter(c => !!c); + if (!completes.length) { + return null; + } - return { - limitHit: completes[0] && completes[0].limitHit, - stats: completes[0].stats, - results: arrays.flatten(completes.map(c => c.results)) - }; - }, errs => { - if (!Array.isArray(errs)) { - errs = [errs]; - } + return { + limitHit: completes[0] && completes[0].limitHit, + stats: completes[0].stats, + results: arrays.flatten(completes.map(c => c.results)) + }; + }, errs => { + if (!Array.isArray(errs)) { + errs = [errs]; + } - errs = errs.filter(e => !!e); - return TPromise.wrapError(errs[0]); - }); - } else { - return searchWithProvider(this.diskSearch); - } + errs = errs.filter(e => !!e); + return TPromise.wrapError(errs[0]); + }); }); combinedPromise = providerPromise.then(value => { From 415a55f87d113dcf17d82b538d8a3163c03f7206 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 27 Jun 2018 07:25:41 +0200 Subject: [PATCH 108/228] fix #53087 --- src/vs/workbench/browser/parts/editor/editorDropTarget.ts | 8 +++++++- .../browser/parts/editor/media/editorgroupview.css | 5 +++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts index d2dafc0cfe3..7e08d42cf2a 100644 --- a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts +++ b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts @@ -57,8 +57,14 @@ class DropOverlay extends Themable { this.container = document.createElement('div'); this.container.id = DropOverlay.OVERLAY_ID; this.container.style.top = `${overlayOffsetHeight}px`; + + // Parent this.groupView.element.appendChild(this.container); - this._register(toDisposable(() => this.groupView.element.removeChild(this.container))); + addClass(this.groupView.element, 'dragged-over'); + this._register(toDisposable(() => { + this.groupView.element.removeChild(this.container); + removeClass(this.groupView.element, 'dragged-over'); + })); // Overlay this.overlay = document.createElement('div'); diff --git a/src/vs/workbench/browser/parts/editor/media/editorgroupview.css b/src/vs/workbench/browser/parts/editor/media/editorgroupview.css index 262514ee87a..e7abc5b7357 100644 --- a/src/vs/workbench/browser/parts/editor/media/editorgroupview.css +++ b/src/vs/workbench/browser/parts/editor/media/editorgroupview.css @@ -13,8 +13,9 @@ opacity: 0.5; /* dimmed to indicate inactive state */ } -.monaco-workbench > .part.editor > .content .editor-group-container.empty.active { - opacity: 1; /* indicate active group through undimmed state */ +.monaco-workbench > .part.editor > .content .editor-group-container.empty.active, +.monaco-workbench > .part.editor > .content .editor-group-container.empty.dragged-over { + opacity: 1; /* indicate active/dragged-over group through undimmed state */ } /* Letterpress */ From 6102bcb3eecd733d74aa5dda0570e0a2ef51ea4e Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 26 Jun 2018 16:27:25 +0200 Subject: [PATCH 109/228] fix #52963 --- src/vs/editor/contrib/snippet/snippetSession.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/snippet/snippetSession.ts b/src/vs/editor/contrib/snippet/snippetSession.ts index 7d5e74e4ad2..ba550ec1d24 100644 --- a/src/vs/editor/contrib/snippet/snippetSession.ts +++ b/src/vs/editor/contrib/snippet/snippetSession.ts @@ -98,10 +98,12 @@ export class OneSnippet { const range = this._editor.getModel().getDecorationRange(id); const currentValue = this._editor.getModel().getValueInRange(range); - operations.push({ range: range, text: placeholder.transform.resolve(currentValue) }); + operations.push(EditOperation.replaceMove(range, placeholder.transform.resolve(currentValue))); } } - this._editor.getModel().applyEdits(operations); + if (operations.length > 0) { + this._editor.executeEdits('snippet.placeholderTransform', operations); + } } if (fwd === true && this._placeholderGroupsIdx < this._placeholderGroups.length - 1) { From 1c7ed6318bac8ca709d0d47e20310c7fa52dcd9a Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 27 Jun 2018 10:43:36 +0200 Subject: [PATCH 110/228] #53043 Disable multi selection in extensions --- .../parts/extensions/electron-browser/extensionsViews.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts index aa36590f720..2fd5061fa0e 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts @@ -86,7 +86,8 @@ export class ExtensionsListView extends ViewletPanel { const delegate = new Delegate(); const renderer = this.instantiationService.createInstance(Renderer); this.list = this.instantiationService.createInstance(WorkbenchPagedList, this.extensionsList, delegate, [renderer], { - ariaLabel: localize('extensions', "Extensions") + ariaLabel: localize('extensions', "Extensions"), + multipleSelectionSupport: false }) as WorkbenchPagedList; this.disposables.push(this.list); From 5718c7e4caf63e626453f5394208abc353dd6757 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 27 Jun 2018 10:47:51 +0200 Subject: [PATCH 111/228] fix #52962 --- .../browser/parts/editor/editorDropTarget.ts | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts index 7e08d42cf2a..c175be4f571 100644 --- a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts +++ b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts @@ -117,7 +117,7 @@ class DropOverlay extends Themable { } // Position overlay - this.positionOverlay(e.offsetX, e.offsetY); + this.positionOverlay(e.offsetX, e.offsetY, isDraggingGroup); }, onDragLeave: e => this.dispose(), @@ -254,12 +254,28 @@ class DropOverlay extends Themable { return (e.ctrlKey && !isMacintosh) || (e.altKey && isMacintosh); } - private positionOverlay(mousePosX: number, mousePosY: number): void { + private positionOverlay(mousePosX: number, mousePosY: number, isDraggingGroup: boolean): void { + const preferSplitVertically = this.accessor.partOptions.openSideBySideDirection === 'right'; + const groupViewWidth = this.groupView.element.clientWidth; const groupViewHeight = this.groupView.element.clientHeight; - const edgeWidthThreshold = groupViewWidth * 0.2; // offer to split 20% around center width - const edgeHeightThreshold = groupViewHeight * 0.2; // offer to split 20% around center height + let edgeWidthThresholdFactor: number; + if (isDraggingGroup) { + edgeWidthThresholdFactor = preferSplitVertically ? 0.3 : 0.1; // give larger threshold when dragging group depending on preferred split direction + } else { + edgeWidthThresholdFactor = 0.1; // 10% threshold to split if dragging editors + } + + let edgeHeightThresholdFactor: number; + if (isDraggingGroup) { + edgeHeightThresholdFactor = preferSplitVertically ? 0.1 : 0.3; // give larger threshold when dragging group depending on preferred split direction + } else { + edgeHeightThresholdFactor = 0.1; // 10% threshold to split if dragging editors + } + + const edgeWidthThreshold = groupViewWidth * edgeWidthThresholdFactor; + const edgeHeightThreshold = groupViewHeight * edgeHeightThresholdFactor; const splitWidthThreshold = groupViewWidth / 3; // offer to split left/right at 33% const splitHeightThreshold = groupViewHeight / 3; // offer to split up/down at 33% @@ -285,7 +301,6 @@ class DropOverlay extends Themable { // | LEFT |-----------------------| RIGHT | // | | SPLIT DOWN | | // ---------------------------------------------- - const preferSplitVertically = this.accessor.partOptions.openSideBySideDirection === 'right'; if (preferSplitVertically) { if (mousePosX < splitWidthThreshold) { splitDirection = GroupDirection.LEFT; From 44c99e23a225a20f477b2bd8b0599e54dbe6d7d7 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 27 Jun 2018 10:53:58 +0200 Subject: [PATCH 112/228] Revert "don't accept suggestions with tab when in snippet mode, fixes #50776" This reverts commit ba476a64b17a657741aff89f0bcfd1b5a2c37178. --- src/vs/editor/contrib/suggest/suggestController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/suggest/suggestController.ts b/src/vs/editor/contrib/suggest/suggestController.ts index e073751d3f3..f92c3459f73 100644 --- a/src/vs/editor/contrib/suggest/suggestController.ts +++ b/src/vs/editor/contrib/suggest/suggestController.ts @@ -355,7 +355,7 @@ registerEditorCommand(new SuggestCommand({ handler: x => x.acceptSelectedSuggestion(), kbOpts: { weight: weight, - kbExpr: ContextKeyExpr.and(EditorContextKeys.textInputFocus, SnippetController2.InSnippetMode.toNegated()), + kbExpr: EditorContextKeys.textInputFocus, primary: KeyCode.Tab } })); From f4e9748b6fc1ab7955c72fa4e619f71a771a6eb8 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 27 Jun 2018 11:12:34 +0200 Subject: [PATCH 113/228] fix #53056 --- src/vs/editor/contrib/documentSymbols/outlineTree.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/contrib/documentSymbols/outlineTree.ts b/src/vs/editor/contrib/documentSymbols/outlineTree.ts index fa58d75a59f..4566e0325ba 100644 --- a/src/vs/editor/contrib/documentSymbols/outlineTree.ts +++ b/src/vs/editor/contrib/documentSymbols/outlineTree.ts @@ -181,11 +181,12 @@ export class OutlineRenderer implements IRenderer { } const { count, topSev } = element.marker; - const color = this._themeService.getTheme().getColor(topSev === MarkerSeverity.Error ? listErrorForeground : listWarningForeground).toString(); + const color = this._themeService.getTheme().getColor(topSev === MarkerSeverity.Error ? listErrorForeground : listWarningForeground); + const cssColor = color ? color.toString() : 'inherit'; // color of the label if (this.renderProblemColors) { - template.labelContainer.style.setProperty('--outline-element-color', color); + template.labelContainer.style.setProperty('--outline-element-color', cssColor); } else { template.labelContainer.style.removeProperty('--outline-element-color'); } @@ -199,14 +200,14 @@ export class OutlineRenderer implements IRenderer { dom.removeClass(template.decoration, 'bubble'); template.decoration.innerText = count < 10 ? count.toString() : '+9'; template.decoration.title = count === 1 ? localize('1.problem', "1 problem in this element") : localize('N.problem', "{0} problems in this element", count); - template.decoration.style.setProperty('--outline-element-color', color); + template.decoration.style.setProperty('--outline-element-color', cssColor); } else { dom.show(template.decoration); dom.addClass(template.decoration, 'bubble'); template.decoration.innerText = '\uf052'; template.decoration.title = localize('deep.problem', "Contains elements with problems"); - template.decoration.style.setProperty('--outline-element-color', color); + template.decoration.style.setProperty('--outline-element-color', cssColor); } } From e908617493569a8725cd7feb9c51f332f5a151ad Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 27 Jun 2018 11:50:27 +0200 Subject: [PATCH 114/228] fix #52975 --- src/vs/workbench/browser/parts/editor/editorPart.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index 412afb3d2da..b1a0975e2f6 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -412,7 +412,10 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor // Prepare grid descriptor to create new grid from const gridDescriptor = createSerializedGrid({ - orientation: this.toGridViewOrientation(layout.orientation, this.gridWidget.orientation), + orientation: this.toGridViewOrientation( + layout.orientation, + (this.gridWidget.orientation === Orientation.VERTICAL) ? Orientation.HORIZONTAL : Orientation.VERTICAL // fix https://github.com/Microsoft/vscode/issues/52975 + ), groups: layout.groups }); From 3d129bb3d6fa625358e5b6ee33d6d27556aa2734 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 27 Jun 2018 11:54:38 +0200 Subject: [PATCH 115/228] fix #53030 --- .../outline/electron-browser/outlinePanel.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/parts/outline/electron-browser/outlinePanel.ts b/src/vs/workbench/parts/outline/electron-browser/outlinePanel.ts index 82f3ebbc1d2..e5e59ceaa1d 100644 --- a/src/vs/workbench/parts/outline/electron-browser/outlinePanel.ts +++ b/src/vs/workbench/parts/outline/electron-browser/outlinePanel.ts @@ -27,7 +27,6 @@ import { ITree } from 'vs/base/parts/tree/browser/tree'; import 'vs/css!./outlinePanel'; import { ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; -import { CursorChangeReason } from 'vs/editor/common/controller/cursorEvents'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents'; @@ -539,7 +538,9 @@ export class OutlinePanel extends ViewletPanel { beforePatternState = undefined; } }; - onInputValueChanged(this._input.value); + if (this._input.value) { + onInputValueChanged(this._input.value); + } this._editorDisposables.push(this._input.onDidChange(onInputValueChanged)); this._editorDisposables.push({ @@ -573,8 +574,16 @@ export class OutlinePanel extends ViewletPanel { })); // feature: reveal editor selection in outline - this._editorDisposables.push(editor.onDidChangeCursorSelection(e => e.reason === CursorChangeReason.Explicit && this._revealEditorSelection(model, e.selection))); this._revealEditorSelection(model, editor.getSelection()); + const versionIdThen = model.textModel.getVersionId(); + this._editorDisposables.push(editor.onDidChangeCursorSelection(e => { + // first check if the document has changed and stop revealing the + // cursor position iff it has -> we will update/recompute the + // outline view then anyways + if (model.textModel.getVersionId() === versionIdThen) { + this._revealEditorSelection(model, e.selection); + } + })); // feature: show markers in outline const updateMarker = (e: URI[], ignoreEmpty?: boolean) => { From 0a71f74b1a5566f6421ee6bda4e6a8f5863ebbe3 Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 27 Jun 2018 11:58:46 +0200 Subject: [PATCH 116/228] no hover feedback for readonly folders fixes #53018 --- .../parts/files/electron-browser/views/explorerViewer.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts b/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts index 05e90164e5b..3b4181ae326 100644 --- a/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts +++ b/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts @@ -885,6 +885,9 @@ export class FileDragAndDrop extends SimpleFileResourceDragAndDrop { // All (target = file/folder) else { if (target.isDirectory) { + if (target.isReadonly) { + return DRAG_OVER_REJECT; + } return fromDesktop || isCopy ? DRAG_OVER_ACCEPT_BUBBLE_DOWN_COPY(true) : DRAG_OVER_ACCEPT_BUBBLE_DOWN(true); } From 889d73ac9ae6bebf26947fed0e9833a66e295519 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 27 Jun 2018 12:04:18 +0200 Subject: [PATCH 117/228] fixes #51660 --- src/vs/workbench/browser/parts/views/panelViewlet.ts | 2 +- .../parts/scm/electron-browser/scmViewlet.ts | 11 ++++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/browser/parts/views/panelViewlet.ts b/src/vs/workbench/browser/parts/views/panelViewlet.ts index f8c1b05ffa2..13be029935a 100644 --- a/src/vs/workbench/browser/parts/views/panelViewlet.ts +++ b/src/vs/workbench/browser/parts/views/panelViewlet.ts @@ -52,7 +52,7 @@ export abstract class ViewletPanel extends Panel implements IView { private _onDidBlur = new Emitter(); readonly onDidBlur: Event = this._onDidBlur.event; - private _onDidChangeTitleArea = new Emitter(); + protected _onDidChangeTitleArea = new Emitter(); readonly onDidChangeTitleArea: Event = this._onDidChangeTitleArea.event; private _isVisible: boolean; diff --git a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts index 980a64faa0a..3a2eda194f7 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts @@ -738,10 +738,6 @@ export class RepositoryPanel extends ViewletPanel { private menus: SCMMenus; private visibilityDisposables: IDisposable[] = []; - get onDidChangeTitle(): Event { - return this.menus.onDidChangeTitle; - } - constructor( id: string, readonly repository: ISCMRepository, @@ -760,6 +756,7 @@ export class RepositoryPanel extends ViewletPanel { ) { super({ id, title: repository.provider.label }, keybindingService, contextMenuService, configurationService); this.menus = instantiationService.createInstance(SCMMenus, repository.provider); + this.menus.onDidChangeTitle(this._onDidChangeTitleArea.fire, this._onDidChangeTitleArea, this.disposables); } render(): void { @@ -1026,7 +1023,7 @@ export class SCMViewlet extends PanelViewlet implements IViewModel, IViewsViewle private mainPanelDisposable: IDisposable = EmptyDisposable; private _repositories: ISCMRepository[] = []; private repositoryPanels: RepositoryPanel[] = []; - private singleRepositoryPanelTitleActionsDisposable: IDisposable = EmptyDisposable; + private singlePanelTitleActionsDisposable: IDisposable = EmptyDisposable; private disposables: IDisposable[] = []; private lastFocusedRepository: ISCMRepository | undefined; @@ -1303,10 +1300,10 @@ export class SCMViewlet extends PanelViewlet implements IViewModel, IViewsViewle // React to menu changes for single view mode if (wasSingleView !== this.isSingleView()) { - this.singleRepositoryPanelTitleActionsDisposable.dispose(); + this.singlePanelTitleActionsDisposable.dispose(); if (this.isSingleView()) { - this.singleRepositoryPanelTitleActionsDisposable = this.repositoryPanels[0].onDidChangeTitle(this.updateTitleArea, this); + this.singlePanelTitleActionsDisposable = this.panels[0].onDidChangeTitleArea(this.updateTitleArea, this); } this.updateTitleArea(); From 641344ec6870781bb129a9c763b788902a34af80 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 27 Jun 2018 12:40:01 +0200 Subject: [PATCH 118/228] fix #52985 --- .../contrib/documentSymbols/outlineModel.ts | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/documentSymbols/outlineModel.ts b/src/vs/editor/contrib/documentSymbols/outlineModel.ts index 91c634a3bd1..f448469a22f 100644 --- a/src/vs/editor/contrib/documentSymbols/outlineModel.ts +++ b/src/vs/editor/contrib/documentSymbols/outlineModel.ts @@ -197,10 +197,33 @@ export class OutlineGroup extends TreeElement { export class OutlineModel extends TreeElement { private static readonly _requests = new LRUCache, model: OutlineModel }>(9, .75); + private static readonly _keys = new class { + + private _counter = 1; + private _data = new WeakMap(); + + for(textModel: ITextModel): string { + return `${textModel.id}/${textModel.getVersionId()}/${this._hash(DocumentSymbolProviderRegistry.all(textModel))}`; + } + + private _hash(providers: DocumentSymbolProvider[]): string { + let result = ''; + for (const provider of providers) { + let n = this._data.get(provider); + if (typeof n === 'undefined') { + n = this._counter++; + this._data.set(provider, n); + } + result += n; + } + return result; + } + }; + static create(textModel: ITextModel): TPromise { - let key = `${textModel.id}/${textModel.getVersionId()}/${DocumentSymbolProviderRegistry.all(textModel).length}`; + let key = this._keys.for(textModel); let data = OutlineModel._requests.get(key); if (!data) { From a1f2a57e1b80c7ed5aa6a6e41b710e2b686bc409 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 27 Jun 2018 13:01:18 +0200 Subject: [PATCH 119/228] check that document is still good to use, #53030 --- src/vs/workbench/parts/outline/electron-browser/outlinePanel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/outline/electron-browser/outlinePanel.ts b/src/vs/workbench/parts/outline/electron-browser/outlinePanel.ts index e5e59ceaa1d..1113acbf69b 100644 --- a/src/vs/workbench/parts/outline/electron-browser/outlinePanel.ts +++ b/src/vs/workbench/parts/outline/electron-browser/outlinePanel.ts @@ -580,7 +580,7 @@ export class OutlinePanel extends ViewletPanel { // first check if the document has changed and stop revealing the // cursor position iff it has -> we will update/recompute the // outline view then anyways - if (model.textModel.getVersionId() === versionIdThen) { + if (!model.textModel.isDisposed() && model.textModel.getVersionId() === versionIdThen) { this._revealEditorSelection(model, e.selection); } })); From 95c70e227f89d0345aa0208ccaafa78f674b8008 Mon Sep 17 00:00:00 2001 From: Andre Weinand Date: Wed, 27 Jun 2018 13:00:42 +0200 Subject: [PATCH 120/228] use DAP 1.30.0 --- package.json | 2 +- .../parts/debug/common/debugProtocol.d.ts | 152 ++++++++++-------- yarn.lock | 6 +- 3 files changed, 85 insertions(+), 75 deletions(-) diff --git a/package.json b/package.json index a770c00f3cf..cc43bb41be9 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "sudo-prompt": "8.2.0", "v8-inspect-profiler": "^0.0.8", "vscode-chokidar": "1.6.2", - "vscode-debugprotocol": "1.28.0", + "vscode-debugprotocol": "1.30.0", "vscode-nsfw": "1.0.17", "vscode-ripgrep": "^1.0.1", "vscode-textmate": "^4.0.1", diff --git a/src/vs/workbench/parts/debug/common/debugProtocol.d.ts b/src/vs/workbench/parts/debug/common/debugProtocol.d.ts index 62b866aa293..1e7e0cad704 100644 --- a/src/vs/workbench/parts/debug/common/debugProtocol.d.ts +++ b/src/vs/workbench/parts/debug/common/debugProtocol.d.ts @@ -18,7 +18,7 @@ declare module DebugProtocol { type: string; } - /** A client or server-initiated request. */ + /** A client or debug adapter initiated request. */ export interface Request extends ProtocolMessage { // type: 'request'; /** The command to execute. */ @@ -27,7 +27,7 @@ declare module DebugProtocol { arguments?: any; } - /** Server-initiated event. */ + /** A debug adapter initiated event. */ export interface Event extends ProtocolMessage { // type: 'event'; /** Type of event. */ @@ -36,7 +36,7 @@ declare module DebugProtocol { body?: any; } - /** Response to a request. */ + /** Response for a request. */ export interface Response extends ProtocolMessage { // type: 'response'; /** Sequence number of the corresponding request. */ @@ -51,16 +51,24 @@ declare module DebugProtocol { body?: any; } + /** On error (whenever 'success' is false), the body can provide more details. */ + export interface ErrorResponse extends Response { + body: { + /** An optional, structured error message. */ + error?: Message; + }; + } + /** Event message for 'initialized' event type. This event indicates that the debug adapter is ready to accept configuration requests (e.g. SetBreakpointsRequest, SetExceptionBreakpointsRequest). - A debug adapter is expected to send this event when it is ready to accept configuration requests (but not before the InitializeRequest has finished). + A debug adapter is expected to send this event when it is ready to accept configuration requests (but not before the 'initialize' request has finished). The sequence of events/requests is as follows: - - adapters sends InitializedEvent (after the InitializeRequest has returned) - - frontend sends zero or more SetBreakpointsRequest - - frontend sends one SetFunctionBreakpointsRequest - - frontend sends a SetExceptionBreakpointsRequest if one or more exceptionBreakpointFilters have been defined (or if supportsConfigurationDoneRequest is not defined or false) + - adapters sends 'initialized' event (after the 'initialize' request has returned) + - frontend sends zero or more 'setBreakpoints' requests + - frontend sends one 'setFunctionBreakpoints' request + - frontend sends a 'setExceptionBreakpoints' request if one or more 'exceptionBreakpointFilters' have been defined (or if 'supportsConfigurationDoneRequest' is not defined or false) - frontend sends other future configuration requests - - frontend sends one ConfigurationDoneRequest to indicate the end of the configuration + - frontend sends one 'configurationDone' request to indicate the end of the configuration. */ export interface InitializedEvent extends Event { // event: 'initialized'; @@ -75,10 +83,10 @@ declare module DebugProtocol { body: { /** The reason for the event. For backward compatibility this string is shown in the UI if the 'description' attribute is missing (but it must not be translated). - Values: 'step', 'breakpoint', 'exception', 'pause', 'entry', etc. + Values: 'step', 'breakpoint', 'exception', 'pause', 'entry', 'goto', etc. */ reason: string; - /** The full reason for the event, e.g. 'Paused on exception'. This string is shown in the UI as is. */ + /** The full reason for the event, e.g. 'Paused on exception'. This string is shown in the UI as is and must be translated. */ description?: string; /** The thread which was stopped. */ threadId?: number; @@ -86,9 +94,9 @@ declare module DebugProtocol { preserveFocusHint?: boolean; /** Additional information. E.g. if reason is 'exception', text contains the exception name. This string is shown in the UI. */ text?: string; - /** If allThreadsStopped is true, a debug adapter can announce that all threads have stopped. - * The client should use this information to enable that all threads can be expanded to access their stacktraces. - * If the attribute is missing or false, only the thread with the given threadId can be expanded. + /** If 'allThreadsStopped' is true, a debug adapter can announce that all threads have stopped. + - The client should use this information to enable that all threads can be expanded to access their stacktraces. + - If the attribute is missing or false, only the thread with the given threadId can be expanded. */ allThreadsStopped?: boolean; }; @@ -97,20 +105,20 @@ declare module DebugProtocol { /** Event message for 'continued' event type. The event indicates that the execution of the debuggee has continued. Please note: a debug adapter is not expected to send this event in response to a request that implies that execution continues, e.g. 'launch' or 'continue'. - It is only necessary to send a ContinuedEvent if there was no previous request that implied this. + It is only necessary to send a 'continued' event if there was no previous request that implied this. */ export interface ContinuedEvent extends Event { // event: 'continued'; body: { /** The thread which was continued. */ threadId: number; - /** If allThreadsContinued is true, a debug adapter can announce that all threads have continued. */ + /** If 'allThreadsContinued' is true, a debug adapter can announce that all threads have continued. */ allThreadsContinued?: boolean; }; } /** Event message for 'exited' event type. - The event indicates that the debuggee has exited. + The event indicates that the debuggee has exited and returns its exit code. */ export interface ExitedEvent extends Event { // event: 'exited'; @@ -120,8 +128,8 @@ declare module DebugProtocol { }; } - /** Event message for 'terminated' event types. - The event indicates that debugging of the debuggee has terminated. + /** Event message for 'terminated' event type. + The event indicates that debugging of the debuggee has terminated. This does **not** mean that the debuggee itself has exited. */ export interface TerminatedEvent extends Event { // event: 'terminated'; @@ -160,7 +168,7 @@ declare module DebugProtocol { category?: string; /** The output to report. */ output: string; - /** If an attribute 'variablesReference' exists and its value is > 0, the output contains objects which can be retrieved by passing variablesReference to the VariablesRequest. */ + /** If an attribute 'variablesReference' exists and its value is > 0, the output contains objects which can be retrieved by passing 'variablesReference' to the 'variables' request. */ variablesReference?: number; /** An optional source location where the output was produced. */ source?: Source; @@ -249,8 +257,8 @@ declare module DebugProtocol { }; } - /** runInTerminal request; value of command field is 'runInTerminal'. - With this request a debug adapter can run a command in a terminal. + /** RunInTerminal request; value of command field is 'runInTerminal'. + This request is sent from the debug adapter to the client to run a command in a terminal. This is typically used to launch the debuggee in a terminal provided by the client. */ export interface RunInTerminalRequest extends Request { // command: 'runInTerminal'; @@ -271,7 +279,7 @@ declare module DebugProtocol { env?: { [key: string]: string | null; }; } - /** Response to Initialize request. */ + /** Response to 'runInTerminal' request. */ export interface RunInTerminalResponse extends Response { body: { /** The process ID. */ @@ -279,15 +287,11 @@ declare module DebugProtocol { }; } - /** On error that is whenever 'success' is false, the body can provide more details. */ - export interface ErrorResponse extends Response { - body: { - /** An optional, structured error message. */ - error?: Message; - }; - } - - /** Initialize request; value of command field is 'initialize'. */ + /** Initialize request; value of command field is 'initialize'. + The 'initialize' request is sent as the first request from the client to the debug adapter in order to configure it with client capabilities and to retrieve capabilities from the debug adapter. + Until the debug adapter has responded to with an 'initialize' response, the client must not send any additional requests or events to the debug adapter. In addition the debug adapter is not allowed to send any requests or events to the client until it has responded with an 'initialize' response. + The 'initialize' request may only be sent once. + */ export interface InitializeRequest extends Request { // command: 'initialize'; arguments: InitializeRequestArguments; @@ -326,16 +330,14 @@ declare module DebugProtocol { } /** ConfigurationDone request; value of command field is 'configurationDone'. - The client of the debug protocol must send this request at the end of the sequence of configuration requests (which was started by the InitializedEvent). + The client of the debug protocol must send this request at the end of the sequence of configuration requests (which was started by the 'initialized' event). */ export interface ConfigurationDoneRequest extends Request { // command: 'configurationDone'; arguments?: ConfigurationDoneArguments; } - /** Arguments for 'configurationDone' request. - The configurationDone request has no standardized attributes. - */ + /** Arguments for 'configurationDone' request. */ export interface ConfigurationDoneArguments { } @@ -343,7 +345,9 @@ declare module DebugProtocol { export interface ConfigurationDoneResponse extends Response { } - /** Launch request; value of command field is 'launch'. */ + /** Launch request; value of command field is 'launch'. + The launch request is sent from the client to the debug adapter to start the debuggee with or without debugging (if 'noDebug' is true). Since launching is debugger/runtime specific, the arguments for this request are not part of this specification. + */ export interface LaunchRequest extends Request { // command: 'launch'; arguments: LaunchRequestArguments; @@ -364,7 +368,9 @@ declare module DebugProtocol { export interface LaunchResponse extends Response { } - /** Attach request; value of command field is 'attach'. */ + /** Attach request; value of command field is 'attach'. + The attach request is sent from the client to the debug adapter to attach to a debuggee that is already running. Since attaching is debugger/runtime specific, the arguments for this request are not part of this specification. + */ export interface AttachRequest extends Request { // command: 'attach'; arguments: AttachRequestArguments; @@ -394,9 +400,7 @@ declare module DebugProtocol { arguments?: RestartArguments; } - /** Arguments for 'restart' request. - The restart request has no standardized attributes. - */ + /** Arguments for 'restart' request. */ export interface RestartArguments { } @@ -404,7 +408,9 @@ declare module DebugProtocol { export interface RestartResponse extends Response { } - /** Disconnect request; value of command field is 'disconnect'. */ + /** Disconnect request; value of command field is 'disconnect'. + The 'disconnect' request is sent from the client to the debug adapter in order to stop debugging. It asks the debug adapter to disconnect from the debuggee and to terminate the debug adapter. If the debuggee has been started with the 'launch' request, the 'disconnect' request terminates the debuggee. If the 'attach' request was used to connect to the debuggee, 'disconnect' does not terminate the debuggee. This behavior can be controlled with the 'terminateDebuggee' (if supported by the debug adapter). + */ export interface DisconnectRequest extends Request { // command: 'disconnect'; arguments?: DisconnectArguments; @@ -426,7 +432,7 @@ declare module DebugProtocol { /** SetBreakpoints request; value of command field is 'setBreakpoints'. Sets multiple breakpoints for a single source and clears all previous breakpoints in that source. To clear all breakpoint for a source, specify an empty array. - When a breakpoint is hit, a StoppedEvent (event type 'breakpoint') is generated. + When a breakpoint is hit, a 'stopped' event (with reason 'breakpoint') is generated. */ export interface SetBreakpointsRequest extends Request { // command: 'setBreakpoints'; @@ -435,7 +441,7 @@ declare module DebugProtocol { /** Arguments for 'setBreakpoints' request. */ export interface SetBreakpointsArguments { - /** The source location of the breakpoints; either source.path or source.reference must be specified. */ + /** The source location of the breakpoints; either 'source.path' or 'source.reference' must be specified. */ source: Source; /** The code locations of the breakpoints. */ breakpoints?: SourceBreakpoint[]; @@ -449,11 +455,11 @@ declare module DebugProtocol { Returned is information about each breakpoint created by this request. This includes the actual code location and whether the breakpoint could be verified. The breakpoints returned are in the same order as the elements of the 'breakpoints' - (or the deprecated 'lines') in the SetBreakpointsArguments. + (or the deprecated 'lines') array in the arguments. */ export interface SetBreakpointsResponse extends Response { body: { - /** Information about the breakpoints. The array elements are in the same order as the elements of the 'breakpoints' (or the deprecated 'lines') in the SetBreakpointsArguments. */ + /** Information about the breakpoints. The array elements are in the same order as the elements of the 'breakpoints' (or the deprecated 'lines') array in the arguments. */ breakpoints: Breakpoint[]; }; } @@ -461,7 +467,7 @@ declare module DebugProtocol { /** SetFunctionBreakpoints request; value of command field is 'setFunctionBreakpoints'. Sets multiple function breakpoints and clears all previous function breakpoints. To clear all function breakpoint, specify an empty array. - When a function breakpoint is hit, a StoppedEvent (event type 'function breakpoint') is generated. + When a function breakpoint is hit, a 'stopped' event (event type 'function breakpoint') is generated. */ export interface SetFunctionBreakpointsRequest extends Request { // command: 'setFunctionBreakpoints'; @@ -485,7 +491,7 @@ declare module DebugProtocol { } /** SetExceptionBreakpoints request; value of command field is 'setExceptionBreakpoints'. - The request configures the debuggers response to thrown exceptions. If an exception is configured to break, a StoppedEvent is fired (event type 'exception'). + The request configures the debuggers response to thrown exceptions. If an exception is configured to break, a 'stopped' event is fired (with reason 'exception'). */ export interface SetExceptionBreakpointsRequest extends Request { // command: 'setExceptionBreakpoints'; @@ -514,21 +520,21 @@ declare module DebugProtocol { /** Arguments for 'continue' request. */ export interface ContinueArguments { - /** Continue execution for the specified thread (if possible). If the backend cannot continue on a single thread but will continue on all threads, it should set the allThreadsContinued attribute in the response to true. */ + /** Continue execution for the specified thread (if possible). If the backend cannot continue on a single thread but will continue on all threads, it should set the 'allThreadsContinued' attribute in the response to true. */ threadId: number; } /** Response to 'continue' request. */ export interface ContinueResponse extends Response { body: { - /** If true, the continue request has ignored the specified thread and continued all threads instead. If this attribute is missing a value of 'true' is assumed for backward compatibility. */ + /** If true, the 'continue' request has ignored the specified thread and continued all threads instead. If this attribute is missing a value of 'true' is assumed for backward compatibility. */ allThreadsContinued?: boolean; }; } /** Next request; value of command field is 'next'. The request starts the debuggee to run again for one step. - The debug adapter first sends the NextResponse and then a StoppedEvent (event type 'step') after the step has completed. + The debug adapter first sends the response and then a 'stopped' event (with reason 'step') after the step has completed. */ export interface NextRequest extends Request { // command: 'next'; @@ -548,7 +554,7 @@ declare module DebugProtocol { /** StepIn request; value of command field is 'stepIn'. The request starts the debuggee to step into a function/method if possible. If it cannot step into a target, 'stepIn' behaves like 'next'. - The debug adapter first sends the StepInResponse and then a StoppedEvent (event type 'step') after the step has completed. + The debug adapter first sends the response and then a 'stopped' event (with reason 'step') after the step has completed. If there are multiple function/method calls (or other targets) on the source line, the optional argument 'targetId' can be used to control into which target the 'stepIn' should occur. The list of possible targets for a given source line can be retrieved via the 'stepInTargets' request. @@ -572,7 +578,7 @@ declare module DebugProtocol { /** StepOut request; value of command field is 'stepOut'. The request starts the debuggee to run again for one step. - The debug adapter first sends the StepOutResponse and then a StoppedEvent (event type 'step') after the step has completed. + The debug adapter first sends the response and then a 'stopped' event (with reason 'step') after the step has completed. */ export interface StepOutRequest extends Request { // command: 'stepOut'; @@ -591,7 +597,7 @@ declare module DebugProtocol { /** StepBack request; value of command field is 'stepBack'. The request starts the debuggee to run one step backwards. - The debug adapter first sends the StepBackResponse and then a StoppedEvent (event type 'step') after the step has completed. Clients should only call this request if the capability supportsStepBack is true. + The debug adapter first sends the response and then a 'stopped' event (with reason 'step') after the step has completed. Clients should only call this request if the capability 'supportsStepBack' is true. */ export interface StepBackRequest extends Request { // command: 'stepBack'; @@ -609,7 +615,7 @@ declare module DebugProtocol { } /** ReverseContinue request; value of command field is 'reverseContinue'. - The request starts the debuggee to run backward. Clients should only call this request if the capability supportsStepBack is true. + The request starts the debuggee to run backward. Clients should only call this request if the capability 'supportsStepBack' is true. */ export interface ReverseContinueRequest extends Request { // command: 'reverseContinue'; @@ -628,7 +634,7 @@ declare module DebugProtocol { /** RestartFrame request; value of command field is 'restartFrame'. The request restarts execution of the specified stackframe. - The debug adapter first sends the RestartFrameResponse and then a StoppedEvent (event type 'restart') after the restart has completed. + The debug adapter first sends the response and then a 'stopped' event (with reason 'restart') after the restart has completed. */ export interface RestartFrameRequest extends Request { // command: 'restartFrame'; @@ -649,7 +655,7 @@ declare module DebugProtocol { The request sets the location where the debuggee will continue to run. This makes it possible to skip the execution of code or to executed code again. The code between the current location and the goto target is not executed but skipped. - The debug adapter first sends the GotoResponse and then a StoppedEvent (event type 'goto'). + The debug adapter first sends the response and then a 'stopped' event with reason 'goto'. */ export interface GotoRequest extends Request { // command: 'goto'; @@ -670,7 +676,7 @@ declare module DebugProtocol { /** Pause request; value of command field is 'pause'. The request suspenses the debuggee. - The debug adapter first sends the PauseResponse and then a StoppedEvent (event type 'pause') after the thread has been paused successfully. + The debug adapter first sends the response and then a 'stopped' event (with reason 'pause') after the thread has been paused successfully. */ export interface PauseRequest extends Request { // command: 'pause'; @@ -687,7 +693,9 @@ declare module DebugProtocol { export interface PauseResponse extends Response { } - /** StackTrace request; value of command field is 'stackTrace'. The request returns a stacktrace from the current execution state. */ + /** StackTrace request; value of command field is 'stackTrace'. + The request returns a stacktrace from the current execution state. + */ export interface StackTraceRequest extends Request { // command: 'stackTrace'; arguments: StackTraceArguments; @@ -770,7 +778,7 @@ declare module DebugProtocol { }; } - /** setVariable request; value of command field is 'setVariable'. + /** SetVariable request; value of command field is 'setVariable'. Set the variable with the given name in the variable container to a new value. */ export interface SetVariableRequest extends Request { @@ -836,7 +844,7 @@ declare module DebugProtocol { }; } - /** Thread request; value of command field is 'threads'. + /** Threads request; value of command field is 'threads'. The request retrieves a list of all threads. */ export interface ThreadsRequest extends Request { @@ -851,7 +859,7 @@ declare module DebugProtocol { }; } - /** Terminate thread request; value of command field is 'terminateThreads'. + /** TerminateThreads request; value of command field is 'terminateThreads'. The request terminates the threads with the given ids. */ export interface TerminateThreadsRequest extends Request { @@ -869,7 +877,9 @@ declare module DebugProtocol { export interface TerminateThreadsResponse extends Response { } - /** Modules can be retrieved from the debug adapter with the ModulesRequest which can either return all modules or a range of modules to support paging. */ + /** Modules request; value of command field is 'modules'. + Modules can be retrieved from the debug adapter with the ModulesRequest which can either return all modules or a range of modules to support paging. + */ export interface ModulesRequest extends Request { // command: 'modules'; arguments: ModulesArguments; @@ -893,15 +903,15 @@ declare module DebugProtocol { }; } - /** Retrieves the set of all sources currently loaded by the debugged process. */ + /** LoadedSources request; value of command field is 'loadedSources'. + Retrieves the set of all sources currently loaded by the debugged process. + */ export interface LoadedSourcesRequest extends Request { // command: 'loadedSources'; arguments?: LoadedSourcesArguments; } - /** Arguments for 'loadedSources' request. - The 'loadedSources' request has no standardized arguments. - */ + /** Arguments for 'loadedSources' request. */ export interface LoadedSourcesArguments { } @@ -1057,7 +1067,7 @@ declare module DebugProtocol { }; } - /** CompletionsRequest request; value of command field is 'completions'. + /** Completions request; value of command field is 'completions'. Returns a list of possible completions for a given caret position and text. The CompletionsRequest may only be called if the 'supportsCompletionsRequest' capability exists and is true. */ @@ -1086,8 +1096,8 @@ declare module DebugProtocol { }; } - /** ExceptionInfoRequest request; value of command field is 'exceptionInfo'. - Retrieves the details of the exception that caused the StoppedEvent to be raised. + /** ExceptionInfo request; value of command field is 'exceptionInfo'. + Retrieves the details of the exception that caused this event to be raised. */ export interface ExceptionInfoRequest extends Request { // command: 'exceptionInfo'; @@ -1269,7 +1279,7 @@ declare module DebugProtocol { export interface Source { /** The short name of the source. Every source returned from the debug adapter has a name. When sending a source to the debug adapter this name is optional. */ name?: string; - /** The path of the source to be shown in the UI. It is only used to locate and load the content of the source if no sourceReference is specified (or its vaule is 0). */ + /** The path of the source to be shown in the UI. It is only used to locate and load the content of the source if no sourceReference is specified (or its value is 0). */ path?: string; /** If sourceReference > 0 the contents of the source must be retrieved through the SourceRequest (even if a path is specified). A sourceReference is only valid for a session, so it must not be used to persist a source. */ sourceReference?: number; diff --git a/yarn.lock b/yarn.lock index 5063f04ed15..9fbd2f626d9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6210,9 +6210,9 @@ vscode-chokidar@1.6.2: optionalDependencies: vscode-fsevents "0.3.8" -vscode-debugprotocol@1.28.0: - version "1.28.0" - resolved "https://registry.yarnpkg.com/vscode-debugprotocol/-/vscode-debugprotocol-1.28.0.tgz#b9fb97c3fb2dadbec78e5c1619ff12bf741ce406" +vscode-debugprotocol@1.30.0: + version "1.30.0" + resolved "https://registry.yarnpkg.com/vscode-debugprotocol/-/vscode-debugprotocol-1.30.0.tgz#ece6d8559733e87bc7a2147b385899777a92af69" vscode-fsevents@0.3.8: version "0.3.8" From 2156b87f358cf6c6cf4bf12b5a6570b04e462284 Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Wed, 27 Jun 2018 14:08:10 +0200 Subject: [PATCH 121/228] Update to new command for F8 (fixes #52186) --- .../electron-browser/editor/vs_code_editor_walkthrough.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/welcome/walkThrough/electron-browser/editor/vs_code_editor_walkthrough.md b/src/vs/workbench/parts/welcome/walkThrough/electron-browser/editor/vs_code_editor_walkthrough.md index 03c8304fa09..b2fd54b2e19 100644 --- a/src/vs/workbench/parts/welcome/walkThrough/electron-browser/editor/vs_code_editor_walkthrough.md +++ b/src/vs/workbench/parts/welcome/walkThrough/electron-browser/editor/vs_code_editor_walkthrough.md @@ -137,7 +137,7 @@ In a large file it can often be useful to collapse sections of code to increase >**Tip:** Folding is based on indentation and as a result can apply to all languages. Simply indent your code to create a foldable section you can fold a certain number of levels with shortcuts like kb(editor.foldLevel1) through to kb(editor.foldLevel5). ### Errors and Warnings -Errors and warnings are highlighted as you edit your code with squiggles. In the sample below you can see a number of syntax errors. By pressing kb(editor.action.marker.next) you can navigate across them in sequence and see the detailed error message. As you correct them the squiggles and scrollbar indicators will update. +Errors and warnings are highlighted as you edit your code with squiggles. In the sample below you can see a number of syntax errors. By pressing kb(editor.action.marker.nextInFiles) you can navigate across them in sequence and see the detailed error message. As you correct them the squiggles and scrollbar indicators will update. ```js // This code has a few syntax errors From 7d8a8450f018db2a09c283ed403feb13a0aeeaec Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 27 Jun 2018 14:28:44 +0200 Subject: [PATCH 122/228] fixes #52957 --- src/vs/workbench/parts/debug/browser/breakpointsView.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/parts/debug/browser/breakpointsView.ts b/src/vs/workbench/parts/debug/browser/breakpointsView.ts index d02f6dfaf11..2f1a6a6c829 100644 --- a/src/vs/workbench/parts/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/parts/debug/browser/breakpointsView.ts @@ -335,11 +335,12 @@ class BreakpointsRenderer implements IRenderer Date: Wed, 27 Jun 2018 14:34:45 +0200 Subject: [PATCH 123/228] jsonc strings are wrong color. Fixes #53010 --- extensions/json/build/update-grammars.js | 2 +- extensions/json/package.json | 2 +- .../json/syntaxes/JSONC.tmLanguage.json | 58 ++++++++--------- .../theme-defaults/themes/light_vs.json | 3 +- .../test/colorize-results/tsconfig_json.json | 64 +++++++++---------- 5 files changed, 64 insertions(+), 65 deletions(-) diff --git a/extensions/json/build/update-grammars.js b/extensions/json/build/update-grammars.js index 191ef935444..d7d92e18258 100644 --- a/extensions/json/build/update-grammars.js +++ b/extensions/json/build/update-grammars.js @@ -33,7 +33,7 @@ function adaptJSON(grammar, replacementScope) { var tsGrammarRepo = 'Microsoft/vscode-JSON.tmLanguage'; updateGrammar.update(tsGrammarRepo, 'JSON.tmLanguage', './syntaxes/JSON.tmLanguage.json'); -updateGrammar.update(tsGrammarRepo, 'JSON.tmLanguage', './syntaxes/JSONC.tmLanguage.json', grammar => adaptJSON(grammar, '.jsonc')); +updateGrammar.update(tsGrammarRepo, 'JSON.tmLanguage', './syntaxes/JSONC.tmLanguage.json', grammar => adaptJSON(grammar, '.json.comments')); diff --git a/extensions/json/package.json b/extensions/json/package.json index 12b3ccf9ef6..af9024bfde9 100644 --- a/extensions/json/package.json +++ b/extensions/json/package.json @@ -58,7 +58,7 @@ }, { "language": "jsonc", - "scopeName": "source.jsonc", + "scopeName": "source.json.comments", "path": "./syntaxes/JSONC.tmLanguage.json" } ], diff --git a/extensions/json/syntaxes/JSONC.tmLanguage.json b/extensions/json/syntaxes/JSONC.tmLanguage.json index cea3c59d5aa..50028ef0f35 100644 --- a/extensions/json/syntaxes/JSONC.tmLanguage.json +++ b/extensions/json/syntaxes/JSONC.tmLanguage.json @@ -6,7 +6,7 @@ ], "version": "https://github.com/Microsoft/vscode-JSON.tmLanguage/commit/9bd83f1c252b375e957203f21793316203f61f70", "name": "JSON with comments", - "scopeName": "source.jsonc", + "scopeName": "source.json.comments", "patterns": [ { "include": "#value" @@ -17,27 +17,27 @@ "begin": "\\[", "beginCaptures": { "0": { - "name": "punctuation.definition.array.begin.jsonc" + "name": "punctuation.definition.array.begin.json.comments" } }, "end": "\\]", "endCaptures": { "0": { - "name": "punctuation.definition.array.end.jsonc" + "name": "punctuation.definition.array.end.json.comments" } }, - "name": "meta.structure.array.jsonc", + "name": "meta.structure.array.json.comments", "patterns": [ { "include": "#value" }, { "match": ",", - "name": "punctuation.separator.array.jsonc" + "name": "punctuation.separator.array.json.comments" }, { "match": "[^\\s\\]]", - "name": "invalid.illegal.expected-array-separator.jsonc" + "name": "invalid.illegal.expected-array-separator.json.comments" } ] }, @@ -47,26 +47,26 @@ "begin": "/\\*\\*(?!/)", "captures": { "0": { - "name": "punctuation.definition.comment.jsonc" + "name": "punctuation.definition.comment.json.comments" } }, "end": "\\*/", - "name": "comment.block.documentation.jsonc" + "name": "comment.block.documentation.json.comments" }, { "begin": "/\\*", "captures": { "0": { - "name": "punctuation.definition.comment.jsonc" + "name": "punctuation.definition.comment.json.comments" } }, "end": "\\*/", - "name": "comment.block.jsonc" + "name": "comment.block.json.comments" }, { "captures": { "1": { - "name": "punctuation.definition.comment.jsonc" + "name": "punctuation.definition.comment.json.comments" } }, "match": "(//).*$\\n?", @@ -76,26 +76,26 @@ }, "constant": { "match": "\\b(?:true|false|null)\\b", - "name": "constant.language.jsonc" + "name": "constant.language.json.comments" }, "number": { "match": "(?x) # turn on extended mode\n -? # an optional minus\n (?:\n 0 # a zero\n | # ...or...\n [1-9] # a 1-9 character\n \\d* # followed by zero or more digits\n )\n (?:\n (?:\n \\. # a period\n \\d+ # followed by one or more digits\n )?\n (?:\n [eE] # an e character\n [+-]? # followed by an option +/-\n \\d+ # followed by one or more digits\n )? # make exponent optional\n )? # make decimal portion optional", - "name": "constant.numeric.jsonc" + "name": "constant.numeric.json.comments" }, "object": { "begin": "\\{", "beginCaptures": { "0": { - "name": "punctuation.definition.dictionary.begin.jsonc" + "name": "punctuation.definition.dictionary.begin.json.comments" } }, "end": "\\}", "endCaptures": { "0": { - "name": "punctuation.definition.dictionary.end.jsonc" + "name": "punctuation.definition.dictionary.end.json.comments" } }, - "name": "meta.structure.dictionary.jsonc", + "name": "meta.structure.dictionary.json.comments", "patterns": [ { "comment": "the JSON object key", @@ -108,16 +108,16 @@ "begin": ":", "beginCaptures": { "0": { - "name": "punctuation.separator.dictionary.key-value.jsonc" + "name": "punctuation.separator.dictionary.key-value.json.comments" } }, "end": "(,)|(?=\\})", "endCaptures": { "1": { - "name": "punctuation.separator.dictionary.pair.jsonc" + "name": "punctuation.separator.dictionary.pair.json.comments" } }, - "name": "meta.structure.dictionary.value.jsonc", + "name": "meta.structure.dictionary.value.json.comments", "patterns": [ { "comment": "the JSON object value", @@ -125,13 +125,13 @@ }, { "match": "[^\\s,]", - "name": "invalid.illegal.expected-dictionary-separator.jsonc" + "name": "invalid.illegal.expected-dictionary-separator.json.comments" } ] }, { "match": "[^\\s\\}]", - "name": "invalid.illegal.expected-dictionary-separator.jsonc" + "name": "invalid.illegal.expected-dictionary-separator.json.comments" } ] }, @@ -139,16 +139,16 @@ "begin": "\"", "beginCaptures": { "0": { - "name": "punctuation.definition.string.begin.jsonc" + "name": "punctuation.definition.string.begin.json.comments" } }, "end": "\"", "endCaptures": { "0": { - "name": "punctuation.definition.string.end.jsonc" + "name": "punctuation.definition.string.end.json.comments" } }, - "name": "string.quoted.double.jsonc", + "name": "string.quoted.double.json.comments", "patterns": [ { "include": "#stringcontent" @@ -159,16 +159,16 @@ "begin": "\"", "beginCaptures": { "0": { - "name": "punctuation.support.type.property-name.begin.jsonc" + "name": "punctuation.support.type.property-name.begin.json.comments" } }, "end": "\"", "endCaptures": { "0": { - "name": "punctuation.support.type.property-name.end.jsonc" + "name": "punctuation.support.type.property-name.end.json.comments" } }, - "name": "string.jsonc support.type.property-name.jsonc", + "name": "string.json.comments support.type.property-name.json.comments", "patterns": [ { "include": "#stringcontent" @@ -179,11 +179,11 @@ "patterns": [ { "match": "(?x) # turn on extended mode\n \\\\ # a literal backslash\n (?: # ...followed by...\n [\"\\\\/bfnrt] # one of these characters\n | # ...or...\n u # a u\n [0-9a-fA-F]{4}) # and four hex digits", - "name": "constant.character.escape.jsonc" + "name": "constant.character.escape.json.comments" }, { "match": "\\\\.", - "name": "invalid.illegal.unrecognized-string-escape.jsonc" + "name": "invalid.illegal.unrecognized-string-escape.json.comments" } ] }, diff --git a/extensions/theme-defaults/themes/light_vs.json b/extensions/theme-defaults/themes/light_vs.json index 23d74f67aa7..4134ce0a4cb 100644 --- a/extensions/theme-defaults/themes/light_vs.json +++ b/extensions/theme-defaults/themes/light_vs.json @@ -291,8 +291,7 @@ }, { "scope": [ - "support.type.property-name.json", - "support.type.property-name.jsonc" + "support.type.property-name.json" ], "settings": { "foreground": "#0451a5" diff --git a/extensions/typescript-basics/test/colorize-results/tsconfig_json.json b/extensions/typescript-basics/test/colorize-results/tsconfig_json.json index 0fbf1b4c28c..69ce07f643d 100644 --- a/extensions/typescript-basics/test/colorize-results/tsconfig_json.json +++ b/extensions/typescript-basics/test/colorize-results/tsconfig_json.json @@ -1,7 +1,7 @@ [ { "c": "{", - "t": "source.jsonc meta.structure.dictionary.jsonc punctuation.definition.dictionary.begin.jsonc", + "t": "source.json.comments meta.structure.dictionary.json.comments punctuation.definition.dictionary.begin.json.comments", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -12,7 +12,7 @@ }, { "c": "\t", - "t": "source.jsonc meta.structure.dictionary.jsonc", + "t": "source.json.comments meta.structure.dictionary.json.comments", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -23,40 +23,40 @@ }, { "c": "\"", - "t": "source.jsonc meta.structure.dictionary.jsonc string.jsonc support.type.property-name.jsonc punctuation.support.type.property-name.begin.jsonc", + "t": "source.json.comments meta.structure.dictionary.json.comments string.json.comments support.type.property-name.json.comments punctuation.support.type.property-name.begin.json.comments", "r": { "dark_plus": "support.type.property-name: #9CDCFE", - "light_plus": "support.type.property-name.jsonc: #0451A5", + "light_plus": "support.type.property-name.json: #0451A5", "dark_vs": "support.type.property-name: #9CDCFE", - "light_vs": "support.type.property-name.jsonc: #0451A5", + "light_vs": "support.type.property-name.json: #0451A5", "hc_black": "support.type.property-name: #D4D4D4" } }, { "c": "compilerOptions", - "t": "source.jsonc meta.structure.dictionary.jsonc string.jsonc support.type.property-name.jsonc", + "t": "source.json.comments meta.structure.dictionary.json.comments string.json.comments support.type.property-name.json.comments", "r": { "dark_plus": "support.type.property-name: #9CDCFE", - "light_plus": "support.type.property-name.jsonc: #0451A5", + "light_plus": "support.type.property-name.json: #0451A5", "dark_vs": "support.type.property-name: #9CDCFE", - "light_vs": "support.type.property-name.jsonc: #0451A5", + "light_vs": "support.type.property-name.json: #0451A5", "hc_black": "support.type.property-name: #D4D4D4" } }, { "c": "\"", - "t": "source.jsonc meta.structure.dictionary.jsonc string.jsonc support.type.property-name.jsonc punctuation.support.type.property-name.end.jsonc", + "t": "source.json.comments meta.structure.dictionary.json.comments string.json.comments support.type.property-name.json.comments punctuation.support.type.property-name.end.json.comments", "r": { "dark_plus": "support.type.property-name: #9CDCFE", - "light_plus": "support.type.property-name.jsonc: #0451A5", + "light_plus": "support.type.property-name.json: #0451A5", "dark_vs": "support.type.property-name: #9CDCFE", - "light_vs": "support.type.property-name.jsonc: #0451A5", + "light_vs": "support.type.property-name.json: #0451A5", "hc_black": "support.type.property-name: #D4D4D4" } }, { "c": ":", - "t": "source.jsonc meta.structure.dictionary.jsonc meta.structure.dictionary.value.jsonc punctuation.separator.dictionary.key-value.jsonc", + "t": "source.json.comments meta.structure.dictionary.json.comments meta.structure.dictionary.value.json.comments punctuation.separator.dictionary.key-value.json.comments", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -67,7 +67,7 @@ }, { "c": " ", - "t": "source.jsonc meta.structure.dictionary.jsonc meta.structure.dictionary.value.jsonc", + "t": "source.json.comments meta.structure.dictionary.json.comments meta.structure.dictionary.value.json.comments", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -78,7 +78,7 @@ }, { "c": "{", - "t": "source.jsonc meta.structure.dictionary.jsonc meta.structure.dictionary.value.jsonc meta.structure.dictionary.jsonc punctuation.definition.dictionary.begin.jsonc", + "t": "source.json.comments meta.structure.dictionary.json.comments meta.structure.dictionary.value.json.comments meta.structure.dictionary.json.comments punctuation.definition.dictionary.begin.json.comments", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -89,7 +89,7 @@ }, { "c": "\t\t", - "t": "source.jsonc meta.structure.dictionary.jsonc meta.structure.dictionary.value.jsonc meta.structure.dictionary.jsonc", + "t": "source.json.comments meta.structure.dictionary.json.comments meta.structure.dictionary.value.json.comments meta.structure.dictionary.json.comments", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -100,40 +100,40 @@ }, { "c": "\"", - "t": "source.jsonc meta.structure.dictionary.jsonc meta.structure.dictionary.value.jsonc meta.structure.dictionary.jsonc string.jsonc support.type.property-name.jsonc punctuation.support.type.property-name.begin.jsonc", + "t": "source.json.comments meta.structure.dictionary.json.comments meta.structure.dictionary.value.json.comments meta.structure.dictionary.json.comments string.json.comments support.type.property-name.json.comments punctuation.support.type.property-name.begin.json.comments", "r": { "dark_plus": "support.type.property-name: #9CDCFE", - "light_plus": "support.type.property-name.jsonc: #0451A5", + "light_plus": "support.type.property-name.json: #0451A5", "dark_vs": "support.type.property-name: #9CDCFE", - "light_vs": "support.type.property-name.jsonc: #0451A5", + "light_vs": "support.type.property-name.json: #0451A5", "hc_black": "support.type.property-name: #D4D4D4" } }, { "c": "target", - "t": "source.jsonc meta.structure.dictionary.jsonc meta.structure.dictionary.value.jsonc meta.structure.dictionary.jsonc string.jsonc support.type.property-name.jsonc", + "t": "source.json.comments meta.structure.dictionary.json.comments meta.structure.dictionary.value.json.comments meta.structure.dictionary.json.comments string.json.comments support.type.property-name.json.comments", "r": { "dark_plus": "support.type.property-name: #9CDCFE", - "light_plus": "support.type.property-name.jsonc: #0451A5", + "light_plus": "support.type.property-name.json: #0451A5", "dark_vs": "support.type.property-name: #9CDCFE", - "light_vs": "support.type.property-name.jsonc: #0451A5", + "light_vs": "support.type.property-name.json: #0451A5", "hc_black": "support.type.property-name: #D4D4D4" } }, { "c": "\"", - "t": "source.jsonc meta.structure.dictionary.jsonc meta.structure.dictionary.value.jsonc meta.structure.dictionary.jsonc string.jsonc support.type.property-name.jsonc punctuation.support.type.property-name.end.jsonc", + "t": "source.json.comments meta.structure.dictionary.json.comments meta.structure.dictionary.value.json.comments meta.structure.dictionary.json.comments string.json.comments support.type.property-name.json.comments punctuation.support.type.property-name.end.json.comments", "r": { "dark_plus": "support.type.property-name: #9CDCFE", - "light_plus": "support.type.property-name.jsonc: #0451A5", + "light_plus": "support.type.property-name.json: #0451A5", "dark_vs": "support.type.property-name: #9CDCFE", - "light_vs": "support.type.property-name.jsonc: #0451A5", + "light_vs": "support.type.property-name.json: #0451A5", "hc_black": "support.type.property-name: #D4D4D4" } }, { "c": ":", - "t": "source.jsonc meta.structure.dictionary.jsonc meta.structure.dictionary.value.jsonc meta.structure.dictionary.jsonc meta.structure.dictionary.value.jsonc punctuation.separator.dictionary.key-value.jsonc", + "t": "source.json.comments meta.structure.dictionary.json.comments meta.structure.dictionary.value.json.comments meta.structure.dictionary.json.comments meta.structure.dictionary.value.json.comments punctuation.separator.dictionary.key-value.json.comments", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -144,7 +144,7 @@ }, { "c": " ", - "t": "source.jsonc meta.structure.dictionary.jsonc meta.structure.dictionary.value.jsonc meta.structure.dictionary.jsonc meta.structure.dictionary.value.jsonc", + "t": "source.json.comments meta.structure.dictionary.json.comments meta.structure.dictionary.value.json.comments meta.structure.dictionary.json.comments meta.structure.dictionary.value.json.comments", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -155,7 +155,7 @@ }, { "c": "\"", - "t": "source.jsonc meta.structure.dictionary.jsonc meta.structure.dictionary.value.jsonc meta.structure.dictionary.jsonc meta.structure.dictionary.value.jsonc string.quoted.double.jsonc punctuation.definition.string.begin.jsonc", + "t": "source.json.comments meta.structure.dictionary.json.comments meta.structure.dictionary.value.json.comments meta.structure.dictionary.json.comments meta.structure.dictionary.value.json.comments string.quoted.double.json.comments punctuation.definition.string.begin.json.comments", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -166,7 +166,7 @@ }, { "c": "es6", - "t": "source.jsonc meta.structure.dictionary.jsonc meta.structure.dictionary.value.jsonc meta.structure.dictionary.jsonc meta.structure.dictionary.value.jsonc string.quoted.double.jsonc", + "t": "source.json.comments meta.structure.dictionary.json.comments meta.structure.dictionary.value.json.comments meta.structure.dictionary.json.comments meta.structure.dictionary.value.json.comments string.quoted.double.json.comments", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -177,7 +177,7 @@ }, { "c": "\"", - "t": "source.jsonc meta.structure.dictionary.jsonc meta.structure.dictionary.value.jsonc meta.structure.dictionary.jsonc meta.structure.dictionary.value.jsonc string.quoted.double.jsonc punctuation.definition.string.end.jsonc", + "t": "source.json.comments meta.structure.dictionary.json.comments meta.structure.dictionary.value.json.comments meta.structure.dictionary.json.comments meta.structure.dictionary.value.json.comments string.quoted.double.json.comments punctuation.definition.string.end.json.comments", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -188,7 +188,7 @@ }, { "c": "\t", - "t": "source.jsonc meta.structure.dictionary.jsonc meta.structure.dictionary.value.jsonc meta.structure.dictionary.jsonc meta.structure.dictionary.value.jsonc", + "t": "source.json.comments meta.structure.dictionary.json.comments meta.structure.dictionary.value.json.comments meta.structure.dictionary.json.comments meta.structure.dictionary.value.json.comments", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -199,7 +199,7 @@ }, { "c": "}", - "t": "source.jsonc meta.structure.dictionary.jsonc meta.structure.dictionary.value.jsonc meta.structure.dictionary.jsonc punctuation.definition.dictionary.end.jsonc", + "t": "source.json.comments meta.structure.dictionary.json.comments meta.structure.dictionary.value.json.comments meta.structure.dictionary.json.comments punctuation.definition.dictionary.end.json.comments", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -210,7 +210,7 @@ }, { "c": "}", - "t": "source.jsonc meta.structure.dictionary.jsonc punctuation.definition.dictionary.end.jsonc", + "t": "source.json.comments meta.structure.dictionary.json.comments punctuation.definition.dictionary.end.json.comments", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", From 39b91f6c674882ef20fbde20b95ed6c01cd3a576 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 27 Jun 2018 14:54:30 +0200 Subject: [PATCH 124/228] fix #53144 --- src/vs/editor/contrib/snippet/snippetParser.ts | 3 +++ src/vs/editor/contrib/snippet/test/snippetParser.test.ts | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/src/vs/editor/contrib/snippet/snippetParser.ts b/src/vs/editor/contrib/snippet/snippetParser.ts index 35e163ea4ff..feb84a18b1f 100644 --- a/src/vs/editor/contrib/snippet/snippetParser.ts +++ b/src/vs/editor/contrib/snippet/snippetParser.ts @@ -643,6 +643,9 @@ export class SnippetParser { let start = this._token; while (this._token.type !== type) { this._token = this._scanner.next(); + if (this._token.type === TokenType.EOF) { + return false; + } } let value = this._scanner.value.substring(start.pos, this._token.pos); this._token = this._scanner.next(); diff --git a/src/vs/editor/contrib/snippet/test/snippetParser.test.ts b/src/vs/editor/contrib/snippet/test/snippetParser.test.ts index b3ab4b6debe..369d3c79d56 100644 --- a/src/vs/editor/contrib/snippet/test/snippetParser.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetParser.test.ts @@ -727,4 +727,9 @@ suite('SnippetParser', () => { transform.regexp = new RegExp('foo', 'gi'); assert.equal(transform.toTextmateString(), '/foo/bar/ig'); }); + + test('Snippet parser freeze #53144', function () { + let snippet = new SnippetParser().parse('${1/(void$)|(.+)/${1:?-\treturn nil;}/}'); + assertMarker(snippet, Placeholder); + }); }); From 9ba606df0ee5c2a386eac532771ac343a62f1985 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 27 Jun 2018 15:01:33 +0200 Subject: [PATCH 125/228] add recrusive-flag to deleteFile-function, #52941 --- src/vs/editor/common/modes.ts | 2 +- src/vs/monaco.d.ts | 1 + src/vs/vscode.proposed.d.ts | 2 +- src/vs/workbench/api/node/extHost.protocol.ts | 4 ++-- src/vs/workbench/api/node/extHostTypes.ts | 16 +++++++++++----- .../bulkEdit/electron-browser/bulkEditService.ts | 1 + 6 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 7905ccba0f4..94f3bf63ed2 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -906,7 +906,7 @@ export function isResourceTextEdit(thing: any): thing is ResourceTextEdit { export interface ResourceFileEdit { oldUri: URI; newUri: URI; - options: { overwrite?: boolean, ignoreIfExists?: boolean }; + options: { overwrite?: boolean, ignoreIfExists?: boolean, recursive?: boolean }; } export interface ResourceTextEdit { diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 7dcf6436363..41851050fb9 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -5191,6 +5191,7 @@ declare namespace monaco.languages { options: { overwrite?: boolean; ignoreIfExists?: boolean; + recursive?: boolean; }; } diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 3372e7e8e2c..d48d52e96ee 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -817,7 +817,7 @@ declare module 'vscode' { * * @param uri The uri of the file that is to be deleted. */ - deleteFile(uri: Uri): void; + deleteFile(uri: Uri, options?: { recursive?: boolean }): void; /** * Rename a file or folder. diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 6aab73ca6f7..f95d1bbd835 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -29,7 +29,7 @@ import { IConfig, IAdapterExecutable, ITerminalSettings } from 'vs/workbench/par import { IQuickPickItem, IPickOptions, IQuickInputButton } from 'vs/platform/quickinput/common/quickInput'; import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles'; import { TextEditorCursorStyle } from 'vs/editor/common/config/editorOptions'; -import { EndOfLine, TextEditorLineNumbersStyle } from 'vs/workbench/api/node/extHostTypes'; +import { EndOfLine, TextEditorLineNumbersStyle, IFileOperationOptions } from 'vs/workbench/api/node/extHostTypes'; import { TaskSet } from 'vs/workbench/parts/tasks/common/tasks'; @@ -774,7 +774,7 @@ export interface WorkspaceSymbolsDto extends IdObject { export interface ResourceFileEditDto { oldUri: UriComponents; newUri: UriComponents; - options: { overwrite?: boolean, ignoreIfExists?: boolean }; + options: IFileOperationOptions; } export interface ResourceTextEditDto { diff --git a/src/vs/workbench/api/node/extHostTypes.ts b/src/vs/workbench/api/node/extHostTypes.ts index 677f2a9ccf4..81e6a53db9a 100644 --- a/src/vs/workbench/api/node/extHostTypes.ts +++ b/src/vs/workbench/api/node/extHostTypes.ts @@ -495,11 +495,17 @@ export class TextEdit { } +export interface IFileOperationOptions { + overwrite?: boolean; + ignoreIfExists?: boolean; + recursive?: boolean; +} + export interface IFileOperation { _type: 1; from: URI; to: URI; - options?: { overwrite?: boolean, ignoreIfExists?: boolean; }; + options?: IFileOperationOptions; } export interface IFileTextEdit { @@ -520,8 +526,8 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit { this._edits.push({ _type: 1, from: undefined, to: uri, options }); } - deleteFile(uri: vscode.Uri): void { - this._edits.push({ _type: 1, from: uri, to: undefined }); + deleteFile(uri: vscode.Uri, options?: { recursive?: boolean }): void { + this._edits.push({ _type: 1, from: uri, to: undefined, options }); } replace(uri: URI, range: Range, newText: string): void { @@ -593,8 +599,8 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit { return values(textEdits); } - _allEntries(): ([URI, TextEdit[]] | [URI, URI, { overwrite?: boolean, ignoreIfExists?: boolean }])[] { - let res: ([URI, TextEdit[]] | [URI, URI, { overwrite?: boolean, ignoreIfExists?: boolean }])[] = []; + _allEntries(): ([URI, TextEdit[]] | [URI, URI, IFileOperationOptions])[] { + let res: ([URI, TextEdit[]] | [URI, URI, IFileOperationOptions])[] = []; for (let edit of this._edits) { if (edit._type === 1) { res.push([edit.from, edit.to, edit.options]); diff --git a/src/vs/workbench/services/bulkEdit/electron-browser/bulkEditService.ts b/src/vs/workbench/services/bulkEdit/electron-browser/bulkEditService.ts index f3f62fdbf02..fcd3687a8c7 100644 --- a/src/vs/workbench/services/bulkEdit/electron-browser/bulkEditService.ts +++ b/src/vs/workbench/services/bulkEdit/electron-browser/bulkEditService.ts @@ -339,6 +339,7 @@ export class BulkEdit { if (edit.newUri && edit.oldUri) { await this._textFileService.move(edit.oldUri, edit.newUri, overwrite); } else if (!edit.newUri && edit.oldUri) { + // let recrusive = edit.options && edit.options.recursive; await this._textFileService.delete(edit.oldUri, true); } else if (edit.newUri && !edit.oldUri) { let ignoreIfExists = edit.options && edit.options.ignoreIfExists; From 5bd8d9f2a8dbe3513a03b86472810af90b8ad274 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 27 Jun 2018 15:08:55 +0200 Subject: [PATCH 126/228] more doc, fixes #52927 --- src/vs/vscode.proposed.d.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index d48d52e96ee..d078e33475f 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -833,6 +833,21 @@ declare module 'vscode' { // deleteText(uri: Uri, range: Range): void; } + export namespace workspace { + /** + * Make changes to one or many resources as defined by the given + * [workspace edit](#WorkspaceEdit). + * + * The editor implements an 'all-or-nothing'-strategy and that means failure to modify, + * delete, rename, or create one file will abort the operation. In that case, the thenable returned + * by this function resolves to `false`. + * + * @param edit A workspace edit. + * @return A thenable that resolves when the edit could be applied. + */ + export function applyEdit(edit: WorkspaceEdit): Thenable; + } + //#endregion //#region mjbvz,joh: https://github.com/Microsoft/vscode/issues/43768 From c6f2eacce8dfb9a0d8e37de0e4f9cdbcdcad2f5d Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 27 Jun 2018 15:12:35 +0200 Subject: [PATCH 127/228] win32 update service should notify url update on zip fixes #52225 --- .../electron-main/updateService.linux.ts | 2 +- .../electron-main/updateService.win32.ts | 37 ++++++++++++++++--- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/vs/platform/update/electron-main/updateService.linux.ts b/src/vs/platform/update/electron-main/updateService.linux.ts index 12d660bb203..71fe0626655 100644 --- a/src/vs/platform/update/electron-main/updateService.linux.ts +++ b/src/vs/platform/update/electron-main/updateService.linux.ts @@ -84,8 +84,8 @@ export class LinuxUpdateService extends AbstractUpdateService { } else { shell.openExternal(state.update.url); } - this.setState(State.Idle); + this.setState(State.Idle); return TPromise.as(null); } } diff --git a/src/vs/platform/update/electron-main/updateService.win32.ts b/src/vs/platform/update/electron-main/updateService.win32.ts index 6ac195ed9de..003a2a5492a 100644 --- a/src/vs/platform/update/electron-main/updateService.win32.ts +++ b/src/vs/platform/update/electron-main/updateService.win32.ts @@ -14,7 +14,7 @@ import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycle import { IRequestService } from 'vs/platform/request/node/request'; import product from 'vs/platform/node/product'; import { TPromise, Promise } from 'vs/base/common/winjs.base'; -import { State, IUpdate, StateType } from 'vs/platform/update/common/update'; +import { State, IUpdate, StateType, AvailableForDownload } from 'vs/platform/update/common/update'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ILogService } from 'vs/platform/log/common/log'; @@ -23,6 +23,7 @@ import { download, asJson } from 'vs/base/node/request'; import { checksum } from 'vs/base/node/crypto'; import { tmpdir } from 'os'; import { spawn } from 'child_process'; +import { shell } from 'electron'; function pollUntil(fn: () => boolean, timeout = 1000): TPromise { return new TPromise(c => { @@ -43,12 +44,18 @@ interface IAvailableUpdate { updateFilePath?: string; } +enum UpdateType { + Automatic, + Manual +} + export class Win32UpdateService extends AbstractUpdateService { _serviceBrand: any; private url: string | undefined; private availableUpdate: IAvailableUpdate | undefined; + private updateType: UpdateType; @memoize get cachePath(): TPromise { @@ -65,13 +72,13 @@ export class Win32UpdateService extends AbstractUpdateService { @ILogService logService: ILogService ) { super(lifecycleService, configurationService, environmentService, logService); + + this.updateType = fs.existsSync(path.join(path.dirname(process.execPath), 'unins000.exe')) + ? UpdateType.Automatic + : UpdateType.Manual; } protected setUpdateFeedUrl(quality: string): boolean { - if (!fs.existsSync(path.join(path.dirname(process.execPath), 'unins000.exe'))) { - return false; - } - let platform = 'win32'; if (process.arch === 'x64') { @@ -96,7 +103,7 @@ export class Win32UpdateService extends AbstractUpdateService { this.requestService.request({ url: this.url }) .then(asJson) .then(update => { - if (!update || !update.url || !update.version) { + if (!update || !update.url || !update.version || !update.productVersion) { /* __GDPR__ "update:notAvailable" : { "explicit" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } @@ -108,6 +115,11 @@ export class Win32UpdateService extends AbstractUpdateService { return TPromise.as(null); } + if (this.updateType === UpdateType.Manual) { + this.setState(State.AvailableForDownload(update)); + return TPromise.as(null); + } + this.setState(State.Downloading(update)); return this.cleanup(update.version).then(() => { @@ -156,6 +168,19 @@ export class Win32UpdateService extends AbstractUpdateService { }); } + protected doDownloadUpdate(state: AvailableForDownload): TPromise { + // Use the download URL if available as we don't currently detect the package type that was + // installed and the website download page is more useful than the tarball generally. + if (product.downloadUrl && product.downloadUrl.length > 0) { + shell.openExternal(product.downloadUrl); + } else { + shell.openExternal(state.update.url); + } + + this.setState(State.Idle); + return TPromise.as(null); + } + private getUpdatePackagePath(version: string): TPromise { return this.cachePath.then(cachePath => path.join(cachePath, `CodeSetup-${product.quality}-${version}.exe`)); } From 6516aa4162f1c540813682d9e494f8b6e625f5f0 Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 27 Jun 2018 15:18:46 +0200 Subject: [PATCH 128/228] fixes #52892 --- src/vs/base/browser/ui/centered/centeredViewLayout.ts | 7 +++++++ src/vs/workbench/browser/parts/editor/editorPart.ts | 8 ++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/vs/base/browser/ui/centered/centeredViewLayout.ts b/src/vs/base/browser/ui/centered/centeredViewLayout.ts index 53230084614..50cac7329f2 100644 --- a/src/vs/base/browser/ui/centered/centeredViewLayout.ts +++ b/src/vs/base/browser/ui/centered/centeredViewLayout.ts @@ -60,8 +60,15 @@ export class CenteredViewLayout { constructor(private container: HTMLElement, private view: IView, public readonly state: CenteredViewState = GOLDEN_RATIO) { this.container.appendChild(this.view.element); + // Make sure to hide the split view overflow like sashes #52892 + this.container.style.overflow = 'hidden'; } + get minimumWidth(): number { return this.splitView ? this.splitView.minimumSize : this.view.minimumWidth; } + get maximumWidth(): number { return this.splitView ? this.splitView.maximumSize : this.view.maximumWidth; } + get minimumHeight(): number { return this.view.minimumHeight; } + get maximumHeight(): number { return this.view.maximumHeight; } + layout(width: number, height: number): void { this.width = width; this.height = height; diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index b1a0975e2f6..28d47fa6e15 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -731,10 +731,10 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor //#region Part - get minimumWidth(): number { return this.gridWidget ? this.gridWidget.minimumWidth : 0; } - get maximumWidth(): number { return this.gridWidget ? this.gridWidget.maximumWidth : Number.POSITIVE_INFINITY; } - get minimumHeight(): number { return this.gridWidget ? this.gridWidget.minimumHeight : 0; } - get maximumHeight(): number { return this.gridWidget ? this.gridWidget.maximumHeight : Number.POSITIVE_INFINITY; } + get minimumWidth(): number { return this.centeredLayoutWidget.minimumWidth; } + get maximumWidth(): number { return this.centeredLayoutWidget.maximumWidth; } + get minimumHeight(): number { return this.centeredLayoutWidget.minimumHeight; } + get maximumHeight(): number { return this.centeredLayoutWidget.maximumHeight; } get preferredSize(): Dimension { if (!this._preferredSize) { From 9e5ab55d9f3ae8ea9d82d2af883a7485bedd26c8 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 27 Jun 2018 15:19:58 +0200 Subject: [PATCH 129/228] Fix #52901 --- src/vs/workbench/api/electron-browser/mainThreadTreeViews.ts | 1 + src/vs/workbench/api/node/extHostTreeViews.ts | 2 +- src/vs/workbench/browser/parts/views/customView.ts | 5 +++++ src/vs/workbench/common/views.ts | 2 ++ 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/api/electron-browser/mainThreadTreeViews.ts b/src/vs/workbench/api/electron-browser/mainThreadTreeViews.ts index f10dd9cc5a0..bbd23cb0e50 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadTreeViews.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadTreeViews.ts @@ -34,6 +34,7 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie if (viewer) { viewer.dataProvider = dataProvider; this.registerListeners(treeViewId, viewer); + this._proxy.$setVisible(treeViewId, viewer.visible); } else { this.notificationService.error('No view is registered with id: ' + treeViewId); } diff --git a/src/vs/workbench/api/node/extHostTreeViews.ts b/src/vs/workbench/api/node/extHostTreeViews.ts index 19b4b163334..a63e56f9972 100644 --- a/src/vs/workbench/api/node/extHostTreeViews.ts +++ b/src/vs/workbench/api/node/extHostTreeViews.ts @@ -125,7 +125,7 @@ class ExtHostTreeView extends Disposable { private elements: Map = new Map(); private nodes: Map = new Map(); - private _visible: boolean = true; + private _visible: boolean = false; get visible(): boolean { return this._visible; } private _selectedHandles: TreeItemHandle[] = []; diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/customView.ts index 1de55d78e61..e50880f4648 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/customView.ts @@ -242,7 +242,12 @@ export class CustomTreeViewer extends Disposable implements ITreeViewer { return this._hasIconForLeafNode; } + get visible(): boolean { + return this.isVisible; + } + setVisibility(isVisible: boolean): void { + isVisible = !!isVisible; if (this.isVisible === isVisible) { return; } diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 47d5a9cfadf..2918f2ec44e 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -229,6 +229,8 @@ export interface ITreeViewer extends IDisposable { readonly onDidChangeVisibility: Event; + readonly visible: boolean; + refresh(treeItems?: ITreeItem[]): TPromise; setVisibility(visible: boolean): void; From db1b88375fb3f006a584255f3a57176b3b044cf9 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 27 Jun 2018 15:21:55 +0200 Subject: [PATCH 130/228] fixes #52982 --- build/gulpfile.vscode.win32.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/gulpfile.vscode.win32.js b/build/gulpfile.vscode.win32.js index 49595d5f078..4f010cb51f1 100644 --- a/build/gulpfile.vscode.win32.js +++ b/build/gulpfile.vscode.win32.js @@ -108,7 +108,7 @@ defineWin32SetupTasks('x64', 'user'); function archiveWin32Setup(arch) { return cb => { - const args = ['a', '-tzip', zipPath(arch), '.', '-r']; + const args = ['a', '-tzip', zipPath(arch), '-x!CodeSignSummary*.md', '.', '-r']; cp.spawn(_7z, args, { stdio: 'inherit', cwd: buildPath(arch) }) .on('error', cb) From 59123833746d26f73dcdaaab2d812d2ed7269511 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 27 Jun 2018 15:26:25 +0200 Subject: [PATCH 131/228] fix #52948 --- src/vs/workbench/electron-browser/window.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index 4552d8a7555..14c87e54598 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -246,12 +246,12 @@ export class ElectronWindow extends Themable { if (e.target instanceof HTMLElement) { const target = e.target; if (target.nodeName && (target.nodeName.toLowerCase() === 'input' || target.nodeName.toLowerCase() === 'textarea')) { - e.preventDefault(); - e.stopPropagation(); + DOM.EventHelper.stop(e, true); this.contextMenuService.showContextMenu({ getAnchor: () => e, - getActions: () => TPromise.as(TextInputActions) + getActions: () => TPromise.as(TextInputActions), + onHide: () => target.focus() // fixes https://github.com/Microsoft/vscode/issues/52948 }); } } From e087b31199659c22c58c0ced4641d1b167ca62e7 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 27 Jun 2018 15:27:32 +0200 Subject: [PATCH 132/228] use data folder for win32 and linux portable mode fixes #53108 --- src/cli.js | 16 ++++++++++++++-- src/main.js | 16 ++++++++++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/cli.js b/src/cli.js index 1bb0476f61f..41d46871f92 100644 --- a/src/cli.js +++ b/src/cli.js @@ -20,8 +20,20 @@ function getApplicationPath() { } } -const portableDataName = product.portable || `${product.applicationName}-portable-data`; -const portableDataPath = path.join(path.dirname(getApplicationPath()), portableDataName); +function getPortableDataPath() { + if (process.env['VSCODE_PORTABLE']) { + return process.env['VSCODE_PORTABLE']; + } + + if (process.platform === 'win32' || process.platform === 'linux') { + return path.join(getApplicationPath(), 'data'); + } else { + const portableDataName = product.portable || `${product.applicationName}-portable-data`; + return path.join(path.dirname(getApplicationPath()), portableDataName); + } +} + +const portableDataPath = getPortableDataPath(); const isPortable = fs.existsSync(portableDataPath); const portableTempPath = path.join(portableDataPath, 'tmp'); const isTempPortable = isPortable && fs.existsSync(portableTempPath); diff --git a/src/main.js b/src/main.js index 2fc84ae20fd..624bf52f0c1 100644 --- a/src/main.js +++ b/src/main.js @@ -27,8 +27,20 @@ function getApplicationPath() { } } -const portableDataName = product.portable || `${product.applicationName}-portable-data`; -const portableDataPath = process.env['VSCODE_PORTABLE'] || path.join(path.dirname(getApplicationPath()), portableDataName); +function getPortableDataPath() { + if (process.env['VSCODE_PORTABLE']) { + return process.env['VSCODE_PORTABLE']; + } + + if (process.platform === 'win32' || process.platform === 'linux') { + return path.join(getApplicationPath(), 'data'); + } else { + const portableDataName = product.portable || `${product.applicationName}-portable-data`; + return path.join(path.dirname(getApplicationPath()), portableDataName); + } +} + +const portableDataPath = getPortableDataPath(); const isPortable = fs.existsSync(portableDataPath); const portableTempPath = path.join(portableDataPath, 'tmp'); const isTempPortable = isPortable && fs.existsSync(portableTempPath); From d2d735cbe3e27bb03afeab793f13fc89a4a0ba5e Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 27 Jun 2018 15:31:31 +0200 Subject: [PATCH 133/228] fixes #53058 --- build/gulpfile.vscode.win32.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/gulpfile.vscode.win32.js b/build/gulpfile.vscode.win32.js index 4f010cb51f1..a9cd1a11b7a 100644 --- a/build/gulpfile.vscode.win32.js +++ b/build/gulpfile.vscode.win32.js @@ -74,7 +74,7 @@ function buildWin32Setup(arch, target) { DirName: product.win32DirName, Version: pkg.version, RawVersion: pkg.version.replace(/-\w+$/, ''), - NameVersion: product.win32NameVersion, + NameVersion: product.win32NameVersion + (target === 'user' ? ' (User)' : ''), ExeBasename: product.nameShort, RegValueName: product.win32RegValueName, ShellNameShort: product.win32ShellNameShort, From 9b52a7273b3337d250cfd3b2ba9d49019049ee56 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 27 Jun 2018 15:33:56 +0200 Subject: [PATCH 134/228] Fix #53069 --- .../electron-browser/extensionsActions.ts | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts index 66f6d8c2e03..66363c9a99b 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts @@ -820,20 +820,22 @@ export class ManageExtensionAction extends Action { } private createMenuActionGroups(): IAction[][] { - return [ - [ - this.instantiationService.createInstance(EnableGloballyAction, EnableGloballyAction.LABEL), - this.instantiationService.createInstance(EnableForWorkspaceAction, EnableForWorkspaceAction.LABEL) - ], - [ - this.instantiationService.createInstance(DisableGloballyAction, DisableGloballyAction.LABEL), - this.instantiationService.createInstance(DisableForWorkspaceAction, DisableForWorkspaceAction.LABEL) - ], - this.extensionManagmentServerService.extensionManagementServers.length > 1 ? [this.instantiationService.createInstance(MultiServerInstallSubMenuAction)] : [], - [ - this.extensionManagmentServerService.extensionManagementServers.length > 1 ? this.instantiationService.createInstance(MultiServerUnInstallSubMenuAction) : this.instantiationService.createInstance(UninstallAction) - ] - ]; + const groups: IAction[][] = []; + groups.push([ + this.instantiationService.createInstance(EnableGloballyAction, EnableGloballyAction.LABEL), + this.instantiationService.createInstance(EnableForWorkspaceAction, EnableForWorkspaceAction.LABEL) + ]); + groups.push([ + this.instantiationService.createInstance(DisableGloballyAction, DisableGloballyAction.LABEL), + this.instantiationService.createInstance(DisableForWorkspaceAction, DisableForWorkspaceAction.LABEL) + ]); + if (this.extensionManagmentServerService.extensionManagementServers.length > 1) { + groups.push([this.instantiationService.createInstance(MultiServerInstallSubMenuAction)]); + groups.push([this.instantiationService.createInstance(MultiServerUnInstallSubMenuAction)]); + } else { + groups.push([this.instantiationService.createInstance(UninstallAction)]); + } + return groups; } private update(): void { From 7dee464eb9e8a7ad9b8334803aec1df8a9e10c42 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 27 Jun 2018 15:36:06 +0200 Subject: [PATCH 135/228] fix #52874 --- .../workbench/api/node/extHostApiCommands.ts | 18 ++++++++++++++---- .../api/extHostApiCommands.test.ts | 2 ++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/api/node/extHostApiCommands.ts b/src/vs/workbench/api/node/extHostApiCommands.ts index d3467d37632..37cd6a1908d 100644 --- a/src/vs/workbench/api/node/extHostApiCommands.ts +++ b/src/vs/workbench/api/node/extHostApiCommands.ts @@ -19,6 +19,7 @@ import { ExtHostCommands } from 'vs/workbench/api/node/extHostCommands'; import { CustomCodeAction } from 'vs/workbench/api/node/extHostLanguageFeatures'; import { ICommandsExecutor, PreviewHTMLAPICommand, OpenFolderAPICommand, DiffAPICommand, OpenAPICommand, RemoveFromRecentlyOpenedAPICommand, SetEditorLayoutAPICommand } from './apiCommands'; import { EditorGroupLayout } from 'vs/workbench/services/group/common/editorGroupsService'; +import { isFalsyOrEmpty } from 'vs/base/common/arrays'; export class ExtHostApiCommands { @@ -411,15 +412,24 @@ export class ExtHostApiCommands { }); } - private _executeDocumentSymbolProvider(resource: URI): Thenable { + private _executeDocumentSymbolProvider(resource: URI): Thenable { const args = { resource }; return this._commands.executeCommand('_executeDocumentSymbolProvider', args).then(value => { - if (value && Array.isArray(value)) { - return value.map(typeConverters.DocumentSymbol.to); + if (isFalsyOrEmpty(value)) { + return undefined; } - return undefined; + let result: vscode.SymbolInformation[] = []; + for (const symbol of value) { + result.push(new types.SymbolInformation( + symbol.name, + typeConverters.SymbolKind.to(symbol.kind), + symbol.containerName, + new types.Location(resource, typeConverters.Range.to(symbol.range)) + )); + } + return result; }); } diff --git a/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts b/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts index 34528d0623d..ba37b81ff7d 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts @@ -332,6 +332,8 @@ suite('ExtHostLanguageFeatureCommands', function () { return commands.executeCommand('vscode.executeDocumentSymbolProvider', model.uri).then(values => { assert.equal(values.length, 2); let [first, second] = values; + assert.ok(first instanceof types.SymbolInformation); + assert.ok(second instanceof types.SymbolInformation); assert.equal(first.name, 'testing2'); assert.equal(second.name, 'testing1'); }); From 2dc0a00c4a5700bef174ac9f5a988f5ef97de70b Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 27 Jun 2018 15:38:27 +0200 Subject: [PATCH 136/228] fixes #52964 --- src/vs/workbench/parts/debug/electron-browser/repl.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/parts/debug/electron-browser/repl.ts b/src/vs/workbench/parts/debug/electron-browser/repl.ts index 8574e52a10e..bb1122bc2f2 100644 --- a/src/vs/workbench/parts/debug/electron-browser/repl.ts +++ b/src/vs/workbench/parts/debug/electron-browser/repl.ts @@ -24,7 +24,7 @@ import { registerEditorAction, ServicesAccessor, EditorAction, EditorCommand, re import { IModelService } from 'vs/editor/common/services/modelService'; import { MenuId } from 'vs/platform/actions/common/actions'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService, ContextKeyExpr, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; @@ -84,6 +84,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati private dimension: dom.Dimension; private replInputHeight: number; private model: ITextModel; + private historyNavigationEnablement: IContextKey; constructor( @IDebugService private debugService: IDebugService, @@ -164,6 +165,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati this.replInputContainer = dom.append(container, $('.repl-input-wrapper')); const { scopedContextKeyService, historyNavigationEnablement } = createAndBindHistoryNavigationWidgetScopedContextKeyService(this.contextKeyService, { target: this.replInputContainer, historyNavigator: this }); + this.historyNavigationEnablement = historyNavigationEnablement; this.toUnbind.push(scopedContextKeyService); CONTEXT_IN_DEBUG_REPL.bindTo(scopedContextKeyService).set(true); @@ -175,7 +177,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati triggerCharacters: ['.'], provideCompletionItems: (model: ITextModel, position: Position, _context: modes.SuggestContext, token: CancellationToken): Thenable => { // Disable history navigation because up and down are used to navigate through the suggest widget - historyNavigationEnablement.set(false); + this.historyNavigationEnablement.set(false); const word = this.replInput.getModel().getWordAtPosition(position); const overwriteBefore = word ? word.word.length : 0; const text = this.replInput.getModel().getLineContent(position.lineNumber); @@ -197,7 +199,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati this.layout(this.dimension); })); this.toUnbind.push(this.replInput.onDidChangeModelContent(() => { - historyNavigationEnablement.set(this.replInput.getModel().getLineCount() === 1); + this.historyNavigationEnablement.set(this.replInput.getModel().getValue() === ''); })); this.toUnbind.push(dom.addStandardDisposableListener(this.replInputContainer, dom.EventType.FOCUS, () => dom.addClass(this.replInputContainer, 'synthetic-focus'))); @@ -210,6 +212,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati this.replInput.setValue(historyInput); // always leave cursor at the end. this.replInput.setPosition({ lineNumber: 1, column: historyInput.length + 1 }); + this.historyNavigationEnablement.set(true); } } From c00b163e8e193138f7def703505ac3987249d764 Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 27 Jun 2018 15:51:20 +0200 Subject: [PATCH 137/228] fixes #52991 --- .../parts/debug/browser/debugActionsWidget.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/parts/debug/browser/debugActionsWidget.ts b/src/vs/workbench/parts/debug/browser/debugActionsWidget.ts index a5e8f96b51d..d4f73022e79 100644 --- a/src/vs/workbench/parts/debug/browser/debugActionsWidget.ts +++ b/src/vs/workbench/parts/debug/browser/debugActionsWidget.ts @@ -34,7 +34,6 @@ import { IDisposable } from 'vs/base/common/lifecycle'; const DEBUG_ACTIONS_WIDGET_POSITION_KEY = 'debug.actionswidgetposition'; const DEBUG_ACTIONS_WIDGET_Y_KEY = 'debug.actionswidgety'; -const HEIGHT = 32; export const debugToolBarBackground = registerColor('debugToolBar.background', { dark: '#333333', @@ -220,10 +219,11 @@ export class DebugActionsWidget extends Themable implements IWorkbenchContributi if (y === undefined) { y = this.storageService.getInteger(DEBUG_ACTIONS_WIDGET_Y_KEY, StorageScope.GLOBAL, 0); } - if ((y < HEIGHT / 2) || (y > HEIGHT + HEIGHT / 2)) { - const moveToTop = y < HEIGHT; - this.setYCoordinate(moveToTop ? 0 : HEIGHT); - this.storageService.store(DEBUG_ACTIONS_WIDGET_Y_KEY, moveToTop ? 0 : 2 * HEIGHT, StorageScope.GLOBAL); + const titleAreaHeight = 35; + if ((y < titleAreaHeight / 2) || (y > titleAreaHeight + titleAreaHeight / 2)) { + const moveToTop = y < titleAreaHeight; + this.setYCoordinate(moveToTop ? 0 : titleAreaHeight); + this.storageService.store(DEBUG_ACTIONS_WIDGET_Y_KEY, moveToTop ? 0 : 2 * titleAreaHeight, StorageScope.GLOBAL); } } From f2bf9539390d97f50cae183b92f9a2bf5be7ffeb Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 27 Jun 2018 15:01:19 +0100 Subject: [PATCH 138/228] detect system installation on user installation fixes #52965 --- build/gulpfile.vscode.win32.js | 3 ++- build/win32/code.iss | 16 ++++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/build/gulpfile.vscode.win32.js b/build/gulpfile.vscode.win32.js index a9cd1a11b7a..5a03f23ba7d 100644 --- a/build/gulpfile.vscode.win32.js +++ b/build/gulpfile.vscode.win32.js @@ -81,7 +81,8 @@ function buildWin32Setup(arch, target) { AppMutex: product.win32MutexName, Arch: arch, AppId: arch === 'ia32' ? ia32AppId : x64AppId, - IncompatibleAppId: arch === 'ia32' ? x64AppId : ia32AppId, + IncompatibleTargetAppId: arch === 'ia32' ? product.win32AppId : product.win32x64AppId, + IncompatibleArchAppId: arch === 'ia32' ? x64AppId : ia32AppId, AppUserId: product.win32AppUserModelId, ArchitecturesAllowed: arch === 'ia32' ? '' : 'x64', ArchitecturesInstallIn64BitMode: arch === 'ia32' ? '' : 'x64', diff --git a/build/win32/code.iss b/build/win32/code.iss index 3126cb76789..552d79f2439 100644 --- a/build/win32/code.iss +++ b/build/win32/code.iss @@ -957,14 +957,26 @@ Root: {#EnvironmentRootKey}; Subkey: "{#EnvironmentKey}"; ValueType: expandsz; V // Don't allow installing conflicting architectures function InitializeSetup(): Boolean; var + RegRootKey: String; RegKey: String; ThisArch: String; AltArch: String; begin Result := True; - if IsWin64 then begin - RegKey := 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\' + copy('{#IncompatibleAppId}', 2, 38) + '_is1'; +#if "user" == InstallTarget + if '{#Arch}' = 'ia32' then RegRootKey := 'HKLM32' else RegRootKey := 'HKLM64'; + RegKey := 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\' + copy('{#IncompatibleTargetAppId}', 2, 38) + '_is1'; + + if RegKeyExists(RegRootKey, RegKey) then begin + if MsgBox('{#NameShort} is already installed on this system for all users. Are you sure you want to install it for this user?', mbConfirmation, MB_YESNO) = IDNO then begin + Result := false; + end; + end; +#endif + + if Result and IsWin64 then begin + RegKey := 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\' + copy('{#IncompatibleArchAppId}', 2, 38) + '_is1'; if '{#Arch}' = 'ia32' then begin Result := not RegKeyExists({#Uninstall64RootKey}, RegKey); From 0a57108b0b19313125a63de642de640c34b79574 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 27 Jun 2018 16:01:22 +0200 Subject: [PATCH 139/228] Fix #52946 --- .../extensionManagement/node/extensionManagementService.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 8a57dd69022..5752c45c955 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -437,7 +437,8 @@ export class ExtensionManagementService extends Disposable implements IExtension private extractAndInstall({ zipPath, id, metadata }: InstallableExtension): TPromise { const tempPath = path.join(this.extensionsPath, `.${id}`); const extensionPath = path.join(this.extensionsPath, id); - return this.extractAndRename(id, zipPath, tempPath, extensionPath) + return pfs.rimraf(extensionPath) + .then(() => this.extractAndRename(id, zipPath, tempPath, extensionPath), e => TPromise.wrapError(new ExtensionManagementError(nls.localize('errorDeleting', "Unable to delete the existing folder '{0}' while installing the extension '{1}'. Please delete the folder manually and try again", extensionPath, id), INSTALL_ERROR_DELETING))) .then(() => { this.logService.info('Installation completed.', id); return this.scanExtension(id, this.extensionsPath, LocalExtensionType.User); From 8f8df0366313eab3b106a4c1eb77b1d95e11c4c7 Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 27 Jun 2018 16:11:08 +0200 Subject: [PATCH 140/228] fixes #52879 --- src/vs/workbench/parts/debug/browser/debugActionsWidget.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/parts/debug/browser/debugActionsWidget.ts b/src/vs/workbench/parts/debug/browser/debugActionsWidget.ts index d4f73022e79..74d89fd4687 100644 --- a/src/vs/workbench/parts/debug/browser/debugActionsWidget.ts +++ b/src/vs/workbench/parts/debug/browser/debugActionsWidget.ts @@ -235,6 +235,7 @@ export class DebugActionsWidget extends Themable implements IWorkbenchContributi private show(): void { if (this.isVisible) { + this.setCoordinates(); return; } if (!this.isBuilt) { From 4fd9ba44b550d9d535df5a9ffb1493826a524fd0 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 27 Jun 2018 16:14:47 +0200 Subject: [PATCH 141/228] Fix #52966 --- src/vs/workbench/parts/search/browser/searchWidget.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/search/browser/searchWidget.ts b/src/vs/workbench/parts/search/browser/searchWidget.ts index 8af51ede386..834a9e2331c 100644 --- a/src/vs/workbench/parts/search/browser/searchWidget.ts +++ b/src/vs/workbench/parts/search/browser/searchWidget.ts @@ -32,6 +32,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { ISearchConfigurationProperties } from 'vs/platform/search/common/search'; import { ContextScopedFindInput, ContextScopedHistoryInputBox } from 'vs/platform/widget/browser/contextScopedHistoryWidget'; +import { Delayer } from 'vs/base/common/async'; export interface ISearchWidgetOptions { value?: string; @@ -94,6 +95,7 @@ export class SearchWidget extends Widget { private replaceActionBar: ActionBar; public replaceInputFocusTracker: dom.IFocusTracker; private replaceInputBoxFocused: IContextKey; + private _replaceHistoryDelayer: Delayer; private ignoreGlobalFindBufferOnNextFocus = false; private previousGlobalFindBufferValue: string; @@ -133,6 +135,7 @@ export class SearchWidget extends Widget { this.replaceActive = Constants.ReplaceActiveKey.bindTo(this.contextKeyService); this.searchInputBoxFocused = Constants.SearchInputBoxFocusedKey.bindTo(this.contextKeyService); this.replaceInputBoxFocused = Constants.ReplaceInputBoxFocusedKey.bindTo(this.contextKeyService); + this._replaceHistoryDelayer = new Delayer(500); this.render(container, options); } @@ -274,7 +277,7 @@ export class SearchWidget extends Widget { this.searchInput.onRegexKeyDown((keyboardEvent: IKeyboardEvent) => this.onRegexKeyDown(keyboardEvent)); this._register(this.onReplaceValueChanged(() => { - this.replaceInput.addToHistory(this.replaceInput.value); + this._replaceHistoryDelayer.trigger(() => this.replaceInput.addToHistory(this.replaceInput.value)); })); this.searchInputFocusTracker = this._register(dom.trackFocus(this.searchInput.inputBox.inputElement)); From 0ec5faceef9005c2cac7a9aa52bdd48da53df3a3 Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 27 Jun 2018 16:21:07 +0200 Subject: [PATCH 142/228] fixes #53089 --- src/vs/workbench/parts/debug/browser/breakpointsView.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/parts/debug/browser/breakpointsView.ts b/src/vs/workbench/parts/debug/browser/breakpointsView.ts index 2f1a6a6c829..a4ad7a4b0f3 100644 --- a/src/vs/workbench/parts/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/parts/debug/browser/breakpointsView.ts @@ -337,8 +337,8 @@ class BreakpointsRenderer implements IRenderer Date: Wed, 27 Jun 2018 07:23:11 -0700 Subject: [PATCH 143/228] Make dimensions and maximumDimensions nullable Fixes #53055 --- 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 d078e33475f..48daf753755 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -399,7 +399,7 @@ declare module 'vscode' { * }; * ``` */ - dimensions: TerminalDimensions; + dimensions: TerminalDimensions | undefined; /** * The maximum dimensions of the terminal, this will be undefined immediately after a @@ -407,7 +407,7 @@ declare module 'vscode' { * Listen to [onDidChangeMaximumDimensions](TerminalRenderer.onDidChangeMaximumDimensions) * to get notified when this value changes. */ - readonly maximumDimensions: TerminalDimensions; + readonly maximumDimensions: TerminalDimensions | undefined; /** * The corressponding [Terminal](#Terminal) for this TerminalRenderer. From 6a44b28026b093b24ebe71992017c337379915ad Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 27 Jun 2018 07:24:26 -0700 Subject: [PATCH 144/228] Fix window.onDidChangeActiveTerminal doc Fixes #52930 --- 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 48daf753755..5c7499cf43b 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -470,7 +470,7 @@ declare module 'vscode' { /** * An [event](#Event) which fires when the [active terminal](#window.activeTerminal) - * has changed. *Note* that the event also fires when the active editor changes + * has changed. *Note* that the event also fires when the active terminal changes * to `undefined`. */ export const onDidChangeActiveTerminal: Event; From 9767a5716ef29e6693855629e3ce627ef6e672fb Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 27 Jun 2018 16:25:53 +0200 Subject: [PATCH 145/228] improve win32 archive update notification --- .../update/electron-main/updateService.win32.ts | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/vs/platform/update/electron-main/updateService.win32.ts b/src/vs/platform/update/electron-main/updateService.win32.ts index 003a2a5492a..1760ee8e552 100644 --- a/src/vs/platform/update/electron-main/updateService.win32.ts +++ b/src/vs/platform/update/electron-main/updateService.win32.ts @@ -85,7 +85,9 @@ export class Win32UpdateService extends AbstractUpdateService { platform += '-x64'; } - if (product.target === 'user') { + if (this.updateType === UpdateType.Manual) { + platform += '-archive'; + } else if (product.target === 'user') { platform += '-user'; } @@ -169,14 +171,7 @@ export class Win32UpdateService extends AbstractUpdateService { } protected doDownloadUpdate(state: AvailableForDownload): TPromise { - // Use the download URL if available as we don't currently detect the package type that was - // installed and the website download page is more useful than the tarball generally. - if (product.downloadUrl && product.downloadUrl.length > 0) { - shell.openExternal(product.downloadUrl); - } else { - shell.openExternal(state.update.url); - } - + shell.openExternal(state.update.url); this.setState(State.Idle); return TPromise.as(null); } From 748d3125c15bb1f34eaf3d95e5a9a016d7517cf8 Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 27 Jun 2018 17:00:14 +0200 Subject: [PATCH 146/228] markers: use label for ui --- .../markers/electron-browser/markersTreeViewer.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts b/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts index 55577952d80..5dee170ab08 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts @@ -19,6 +19,8 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { attachBadgeStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IDisposable } from 'vs/base/common/lifecycle'; +import { getPathLabel } from 'vs/base/common/labels'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; interface IResourceMarkersTemplateData { resourceLabel: ResourceLabel; @@ -93,7 +95,8 @@ export class Renderer implements IRenderer { constructor( @IInstantiationService private instantiationService: IInstantiationService, - @IThemeService private themeService: IThemeService + @IThemeService private themeService: IThemeService, + @IEnvironmentService private environmentService: IEnvironmentService ) { } @@ -197,7 +200,7 @@ export class Renderer implements IRenderer { if (templateData.resourceLabel instanceof FileLabel) { templateData.resourceLabel.setFile(element.uri, { matches: element.uriMatches }); } else { - templateData.resourceLabel.setLabel({ name: element.name, description: element.uri.toString(), resource: element.uri }, { matches: element.uriMatches }); + templateData.resourceLabel.setLabel({ name: element.name, description: getPathLabel(element.uri, this.environmentService), resource: element.uri }, { matches: element.uriMatches }); } (templateData).count.setCount(element.filteredCount); } @@ -217,7 +220,7 @@ export class Renderer implements IRenderer { private renderRelatedInfoElement(tree: ITree, element: RelatedInformation, templateData: IRelatedInformationTemplateData) { templateData.resourceLabel.set(paths.basename(element.raw.resource.fsPath), element.uriMatches); - templateData.resourceLabel.element.title = element.raw.resource.toString(); + templateData.resourceLabel.element.title = getPathLabel(element.raw.resource, this.environmentService); templateData.lnCol.textContent = Messages.MARKERS_PANEL_AT_LINE_COL_NUMBER(element.raw.startLineNumber, element.raw.startColumn); templateData.description.set(element.raw.message, element.messageMatches); templateData.description.element.title = element.raw.message; @@ -266,4 +269,3 @@ export class MarkersTreeAccessibilityProvider implements IAccessibilityProvider return null; } } - From 9aa90a526d8e2e6fdb627babf1d82981592c7518 Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Wed, 27 Jun 2018 17:09:09 +0200 Subject: [PATCH 147/228] Fix events firing multiple times (fixes #52894) --- .../src/singlefolder-tests/quickInput.test.ts | 55 ++++++++++++++++--- .../src/singlefolder-tests/window.test.ts | 18 +++++- .../browser/parts/quickinput/quickInput.ts | 20 +++++-- 3 files changed, 79 insertions(+), 14 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/quickInput.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/quickInput.test.ts index 7211b8f397c..04f8ba296e3 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/quickInput.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/quickInput.test.ts @@ -13,7 +13,10 @@ interface QuickPickExpected { events: string[]; activeItems: string[][]; selectionItems: string[][]; - acceptedItems: string[][]; + acceptedItems: { + active: string[][]; + selection: string[][]; + }; } suite('window namespace tests', function () { @@ -31,8 +34,11 @@ suite('window namespace tests', function () { events: ['active', 'active', 'selection', 'accept', 'hide'], activeItems: [['eins'], ['zwei']], selectionItems: [['zwei']], - acceptedItems: [['zwei']], - }, done); + acceptedItems: { + active: [['zwei']], + selection: [['zwei']] + }, + }, (err?: any) => done(err)); quickPick.items = ['eins', 'zwei', 'drei'].map(label => ({ label })); quickPick.show(); @@ -53,8 +59,11 @@ suite('window namespace tests', function () { events: ['active', 'selection', 'accept', 'hide'], activeItems: [['zwei']], selectionItems: [['zwei']], - acceptedItems: [['zwei']], - }, done); + acceptedItems: { + active: [['zwei']], + selection: [['zwei']] + }, + }, (err?: any) => done(err)); quickPick.items = ['eins', 'zwei', 'drei'].map(label => ({ label })); quickPick.activeItems = [quickPick.items[1]]; quickPick.show(); @@ -64,6 +73,35 @@ suite('window namespace tests', function () { })() .catch(err => done(err)); }); + + test('createQuickPick, select first and second', function (_done) { + let done = (err?: any) => { + done = () => {}; + _done(err); + }; + + const quickPick = createQuickPick({ + events: ['active', 'selection', 'active', 'selection', 'accept', 'hide'], + activeItems: [['eins'], ['zwei']], + selectionItems: [['eins'], ['eins', 'zwei']], + acceptedItems: { + active: [['zwei']], + selection: [['eins', 'zwei']] + }, + }, (err?: any) => done(err)); + quickPick.canSelectMany = true; + quickPick.items = ['eins', 'zwei', 'drei'].map(label => ({ label })); + quickPick.show(); + + (async () => { + await commands.executeCommand('workbench.action.quickOpenSelectNext'); + await commands.executeCommand('workbench.action.quickPickManyToggle'); + await commands.executeCommand('workbench.action.quickOpenSelectNext'); + await commands.executeCommand('workbench.action.quickPickManyToggle'); + await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem'); + })() + .catch(err => done(err)); + }); }); }); @@ -92,9 +130,10 @@ function createQuickPick(expected: QuickPickExpected, done: (err?: any) => void) quickPick.onDidAccept(() => { try { assert.equal('accept', expected.events.shift()); - const expectedItems = expected.acceptedItems.shift(); - assert.deepEqual(quickPick.activeItems.map(item => item.label), expectedItems); - assert.deepEqual(quickPick.selectedItems.map(item => item.label), expectedItems); + const expectedActive = expected.acceptedItems.active.shift(); + assert.deepEqual(quickPick.activeItems.map(item => item.label), expectedActive); + const expectedSelection = expected.acceptedItems.selection.shift(); + assert.deepEqual(quickPick.selectedItems.map(item => item.label), expectedSelection); quickPick.dispose(); } catch (err) { done(err); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts index 2a149aa2e1a..27877a8608a 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts @@ -410,9 +410,14 @@ suite('window namespace tests', () => { test('showQuickPick, accept second', async function () { const resolves: ((value: string) => void)[] = []; + let done: () => void; + const unexpected = new Promise((resolve, reject) => { + done = () => resolve(); + resolves.push(reject); + }); const first = new Promise(resolve => resolves.push(resolve)); const pick = window.showQuickPick(['eins', 'zwei', 'drei'], { - onDidSelectItem: item => resolves.shift()!(item as string) + onDidSelectItem: item => resolves.pop()!(item as string) }); assert.equal(await first, 'eins'); const second = new Promise(resolve => resolves.push(resolve)); @@ -420,12 +425,19 @@ suite('window namespace tests', () => { assert.equal(await second, 'zwei'); await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem'); assert.equal(await pick, 'zwei'); + done!(); + return unexpected; }); test('showQuickPick, select first two', async function () { const resolves: ((value: string) => void)[] = []; + let done: () => void; + const unexpected = new Promise((resolve, reject) => { + done = () => resolve(); + resolves.push(reject); + }); const picks = window.showQuickPick(['eins', 'zwei', 'drei'], { - onDidSelectItem: item => resolves.shift()!(item as string), + onDidSelectItem: item => resolves.pop()!(item as string), canPickMany: true }); const first = new Promise(resolve => resolves.push(resolve)); @@ -438,6 +450,8 @@ suite('window namespace tests', () => { await commands.executeCommand('workbench.action.quickPickManyToggle'); await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem'); assert.deepStrictEqual(await picks, ['eins', 'zwei']); + done!(); + return unexpected; }); test('showQuickPick, keep selection (Microsoft/vscode-azure-account#67)', async function () { diff --git a/src/vs/workbench/browser/parts/quickinput/quickInput.ts b/src/vs/workbench/browser/parts/quickinput/quickInput.ts index 23c7d23e53e..35ab51f2050 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInput.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInput.ts @@ -41,6 +41,7 @@ import { Action } from 'vs/base/common/actions'; import URI from 'vs/base/common/uri'; import { IdGenerator } from 'vs/base/common/idGenerator'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { equals } from 'vs/base/common/arrays'; const $ = dom.$; @@ -289,9 +290,11 @@ class QuickPick extends QuickInput implements IQuickPi private _matchOnDetail = false; private _activeItems: T[] = []; private activeItemsUpdated = false; + private activeItemsToConfirm: T[] = []; private onDidChangeActiveEmitter = new Emitter(); private _selectedItems: T[] = []; private selectedItemsUpdated = false; + private selectedItemsToConfirm: T[] = []; private onDidChangeSelectionEmitter = new Emitter(); private quickNavigate = false; @@ -429,8 +432,7 @@ class QuickPick extends QuickInput implements IQuickPi if (this.activeItemsUpdated) { return; // Expect another event. } - // Drop initial event. - if (!focusedItems.length && !this._activeItems.length) { + if (this.activeItemsToConfirm !== this._activeItems && equals(focusedItems, this._activeItems, (a, b) => a === b)) { return; } this._activeItems = focusedItems as T[]; @@ -440,8 +442,7 @@ class QuickPick extends QuickInput implements IQuickPi if (this.canSelectMany) { return; } - // Drop initial event. - if (!selectedItems.length && !this._selectedItems.length) { + if (this.selectedItemsToConfirm !== this._selectedItems && equals(selectedItems, this._selectedItems, (a, b) => a === b)) { return; } this._selectedItems = selectedItems as T[]; @@ -452,6 +453,9 @@ class QuickPick extends QuickInput implements IQuickPi if (!this.canSelectMany) { return; } + if (this.selectedItemsToConfirm !== this._selectedItems && equals(checkedItems, this._selectedItems, (a, b) => a === b)) { + return; + } this._selectedItems = checkedItems as T[]; this.onDidChangeSelectionEmitter.fire(checkedItems as T[]); }), @@ -490,15 +494,23 @@ class QuickPick extends QuickInput implements IQuickPi } if (this.activeItemsUpdated) { this.activeItemsUpdated = false; + this.activeItemsToConfirm = this._activeItems; this.ui.list.setFocusedElements(this.activeItems); + if (this.activeItemsToConfirm === this._activeItems) { + this.activeItemsToConfirm = null; + } } if (this.selectedItemsUpdated) { this.selectedItemsUpdated = false; + this.selectedItemsToConfirm = this._selectedItems; if (this.canSelectMany) { this.ui.list.setCheckedElements(this.selectedItems); } else { this.ui.list.setSelectedElements(this.selectedItems); } + if (this.selectedItemsToConfirm === this._selectedItems) { + this.selectedItemsToConfirm = null; + } } this.ui.list.matchOnDescription = this.matchOnDescription; this.ui.list.matchOnDetail = this.matchOnDetail; From 9a00a108a0b7437ee154a2f51ada43dfac3ea597 Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Wed, 27 Jun 2018 17:16:05 +0200 Subject: [PATCH 148/228] Select whole value when valueSelection is undefined (fixes #52906) This reverts commit c205df5c77b03269f3181505754af65c87fce176. --- src/vs/workbench/browser/parts/quickinput/quickInput.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/browser/parts/quickinput/quickInput.ts b/src/vs/workbench/browser/parts/quickinput/quickInput.ts index 35ab51f2050..669ef71f18d 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInput.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInput.ts @@ -573,7 +573,7 @@ class InputBox extends QuickInput implements IInputBox { private _value = ''; private _valueSelection: Readonly<[number, number]>; - private valueSelectionUpdated = false; + private valueSelectionUpdated = true; private _placeholder: string; private _password = false; private _prompt: string; @@ -674,9 +674,7 @@ class InputBox extends QuickInput implements IInputBox { } if (this.valueSelectionUpdated) { this.valueSelectionUpdated = false; - this.ui.inputBox.select(this._valueSelection ? - { start: this._valueSelection[0], end: this._valueSelection[1] } : - { start: this.ui.inputBox.value.length, end: this.ui.inputBox.value.length }); + this.ui.inputBox.select(this._valueSelection && { start: this._valueSelection[0], end: this._valueSelection[1] }); } if (this.ui.inputBox.placeholder !== (this.placeholder || '')) { this.ui.inputBox.placeholder = (this.placeholder || ''); From d90f82140439e38e1aafc9dc052691dd38f77901 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 27 Jun 2018 17:26:05 +0200 Subject: [PATCH 149/228] fix #52576 --- src/vs/workbench/browser/parts/menubar/menubarPart.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/browser/parts/menubar/menubarPart.ts b/src/vs/workbench/browser/parts/menubar/menubarPart.ts index c505adab959..2f287de3d41 100644 --- a/src/vs/workbench/browser/parts/menubar/menubarPart.ts +++ b/src/vs/workbench/browser/parts/menubar/menubarPart.ts @@ -362,9 +362,11 @@ export class MenubarPart extends Part { titleElement.attr('aria-label', legibleTitle); titleElement.attr('role', 'menu'); - let mnemonic = (/&&(.)/g).exec(this.topLevelTitles[menuTitle])[1]; - if (mnemonic && this.currentEnableMenuBarMnemonics) { - this.registerMnemonic(titleElement.getHTMLElement(), mnemonic); + if (this.currentEnableMenuBarMnemonics) { + let mnemonic = (/&&(.)/g).exec(this.topLevelTitles[menuTitle]); + if (mnemonic && mnemonic[1]) { + this.registerMnemonic(titleElement.getHTMLElement(), mnemonic[1]); + } } this.customMenus.push({ From ba21a32454b563e48701a3a72976fa3164c8479a Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 27 Jun 2018 17:34:17 +0200 Subject: [PATCH 150/228] breakpoint view: fix title --- .../parts/debug/browser/breakpointsView.ts | 47 ++++++++----------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/src/vs/workbench/parts/debug/browser/breakpointsView.ts b/src/vs/workbench/parts/debug/browser/breakpointsView.ts index a4ad7a4b0f3..ba6e7cb7b92 100644 --- a/src/vs/workbench/parts/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/parts/debug/browser/breakpointsView.ts @@ -330,18 +330,12 @@ class BreakpointsRenderer implements IRenderer Date: Wed, 27 Jun 2018 17:44:58 +0200 Subject: [PATCH 151/228] Validate initial value (fixes #52908) --- .../workbench/browser/parts/quickinput/quickInput.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/browser/parts/quickinput/quickInput.ts b/src/vs/workbench/browser/parts/quickinput/quickInput.ts index 669ef71f18d..c5ee214aa43 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInput.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInput.ts @@ -971,27 +971,33 @@ export class QuickInputService extends Component implements IQuickInputService { const input = this.createInputBox(); const validateInput = options.validateInput || (() => TPromise.as(undefined)); const onDidValueChange = debounceEvent(input.onDidChangeValue, (last, cur) => cur, 100); - let validationValue: string; - let validation = TPromise.as(''); + let validationValue = options.value || ''; + let validation = TPromise.wrap(validateInput(validationValue)); const disposables = [ input, onDidValueChange(value => { if (value !== validationValue) { validation = TPromise.wrap(validateInput(value)); + validationValue = value; } validation.then(result => { - input.validationMessage = result; + if (value === validationValue) { + input.validationMessage = result; + } }); }), input.onDidAccept(() => { const value = input.value; if (value !== validationValue) { validation = TPromise.wrap(validateInput(value)); + validationValue = value; } validation.then(result => { if (!result) { resolve(value); input.hide(); + } else if (value === validationValue) { + input.validationMessage = result; } }); }), From 279ef47df2f5578ca519751eb7a423c72ba082fb Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 27 Jun 2018 08:47:57 -0700 Subject: [PATCH 152/228] Null check RegExp exec result Fixes #53153 --- .../parts/terminal/electron-browser/terminalService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts index b7fd6898f18..5b865bd3a86 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts @@ -208,7 +208,7 @@ export class TerminalService extends AbstractTerminalService implements ITermina const osVersion = (/(\d+)\.(\d+)\.(\d+)/g).exec(os.release()); let useWSLexe = false; - if (osVersion.length === 4) { + if (osVersion && osVersion.length === 4) { const buildNumber = parseInt(osVersion[3]); if (buildNumber >= 16299) { useWSLexe = true; From 88ef75874285c45d5cc5d01ddb644c12158ffe31 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 27 Jun 2018 19:10:23 +0200 Subject: [PATCH 153/228] Fix #52961 --- src/vs/base/browser/ui/inputbox/inputBox.ts | 40 +++++++++++++++---- src/vs/base/common/history.ts | 10 ++--- src/vs/editor/contrib/find/findWidget.ts | 4 +- .../editor/contrib/find/simpleFindWidget.ts | 4 +- .../electron-browser/markersPanelActions.ts | 5 +-- .../search/browser/patternInputWidget.ts | 5 +-- .../parts/search/browser/searchWidget.ts | 6 +-- 7 files changed, 45 insertions(+), 29 deletions(-) diff --git a/src/vs/base/browser/ui/inputbox/inputBox.ts b/src/vs/base/browser/ui/inputbox/inputBox.ts index e13ed5bca67..de20e20ffc6 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.ts +++ b/src/vs/base/browser/ui/inputbox/inputBox.ts @@ -513,9 +513,9 @@ export class HistoryInputBox extends InputBox implements IHistoryNavigationWidge this.history = new HistoryNavigator(options.history, 100); } - public addToHistory(value: string): void { - if (value !== this.history.current()) { - this.history.add(value); + public addToHistory(): void { + if (this.value && this.value !== this.getCurrentValue()) { + this.history.add(this.value); } } @@ -524,17 +524,26 @@ export class HistoryInputBox extends InputBox implements IHistoryNavigationWidge } public showNextValue(): void { - let next = this.history.next(); + let next = this.getNextValue(); + if (next) { + next = next === this.value ? this.getNextValue() : next; + } + if (next) { this.value = next; } } public showPreviousValue(): void { - if (this.value.length !== 0) { - this.history.addIfNotPresent(this.value); + if (!this.history.has(this.value)) { + this.addToHistory(); } - const previous = this.history.previous(); + + let previous = this.getPreviousValue(); + if (previous) { + previous = previous === this.value ? this.getPreviousValue() : previous; + } + if (previous) { this.value = previous; } @@ -543,4 +552,21 @@ export class HistoryInputBox extends InputBox implements IHistoryNavigationWidge public clearHistory(): void { this.history.clear(); } + + private getCurrentValue(): string { + let currentValue = this.history.current(); + if (!currentValue) { + currentValue = this.history.last(); + this.history.next(); + } + return currentValue; + } + + private getPreviousValue(): string { + return this.history.previous() || this.history.first(); + } + + private getNextValue(): string { + return this.history.next() || this.history.last(); + } } diff --git a/src/vs/base/common/history.ts b/src/vs/base/common/history.ts index b6546800a13..5bc83979f67 100644 --- a/src/vs/base/common/history.ts +++ b/src/vs/base/common/history.ts @@ -27,12 +27,6 @@ export class HistoryNavigator implements INavigator { this._onChange(); } - public addIfNotPresent(t: T) { - if (!this._history.has(t)) { - this.add(t); - } - } - public next(): T { return this._navigator.next(); } @@ -57,6 +51,10 @@ export class HistoryNavigator implements INavigator { return this._navigator.last(); } + public has(t: T): boolean { + return this._history.has(t); + } + public clear(): void { this._initialize([]); this._onChange(); diff --git a/src/vs/editor/contrib/find/findWidget.ts b/src/vs/editor/contrib/find/findWidget.ts index d4e544d2569..86cac532d3f 100644 --- a/src/vs/editor/contrib/find/findWidget.ts +++ b/src/vs/editor/contrib/find/findWidget.ts @@ -312,10 +312,10 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas private _updateHistory() { if (this._state.searchString) { - this._findInput.inputBox.addToHistory(this._state.searchString); + this._findInput.inputBox.addToHistory(); } if (this._state.replaceString) { - this._replaceInputBox.addToHistory(this._state.replaceString); + this._replaceInputBox.addToHistory(); } } diff --git a/src/vs/editor/contrib/find/simpleFindWidget.ts b/src/vs/editor/contrib/find/simpleFindWidget.ts index fdb0b83104c..adb0cd679c8 100644 --- a/src/vs/editor/contrib/find/simpleFindWidget.ts +++ b/src/vs/editor/contrib/find/simpleFindWidget.ts @@ -201,9 +201,7 @@ export abstract class SimpleFindWidget extends Widget { } protected _updateHistory() { - if (this.inputValue) { - this._findInput.inputBox.addToHistory(this._findInput.getValue()); - } + this._findInput.inputBox.addToHistory(); } } diff --git a/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts b/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts index e40d795aedd..b6479f0a8ec 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts @@ -203,10 +203,7 @@ export class MarkersFilterActionItem extends BaseActionItem { } private onDidInputChange() { - const filterText = this.filterInputBox.value; - if (filterText) { - this.filterInputBox.addToHistory(filterText); - } + this.filterInputBox.addToHistory(); this._onDidChange.fire(); this.reportFilteringUsed(); } diff --git a/src/vs/workbench/parts/search/browser/patternInputWidget.ts b/src/vs/workbench/parts/search/browser/patternInputWidget.ts index 98e9959aaed..ced2a9bbba2 100644 --- a/src/vs/workbench/parts/search/browser/patternInputWidget.ts +++ b/src/vs/workbench/parts/search/browser/patternInputWidget.ts @@ -130,10 +130,7 @@ export class PatternInputWidget extends Widget { } public onSearchSubmit(): void { - const value = this.getValue(); - if (value) { - this.inputBox.addToHistory(value); - } + this.inputBox.addToHistory(); } public showNextTerm() { diff --git a/src/vs/workbench/parts/search/browser/searchWidget.ts b/src/vs/workbench/parts/search/browser/searchWidget.ts index 834a9e2331c..d39a92c8c7a 100644 --- a/src/vs/workbench/parts/search/browser/searchWidget.ts +++ b/src/vs/workbench/parts/search/browser/searchWidget.ts @@ -271,13 +271,13 @@ export class SearchWidget extends Widget { this.searchInput.setCaseSensitive(!!options.isCaseSensitive); this.searchInput.setWholeWords(!!options.isWholeWords); this._register(this.onSearchSubmit(() => { - this.searchInput.inputBox.addToHistory(this.searchInput.getValue()); + this.searchInput.inputBox.addToHistory(); })); this.searchInput.onCaseSensitiveKeyDown((keyboardEvent: IKeyboardEvent) => this.onCaseSensitiveKeyDown(keyboardEvent)); this.searchInput.onRegexKeyDown((keyboardEvent: IKeyboardEvent) => this.onRegexKeyDown(keyboardEvent)); this._register(this.onReplaceValueChanged(() => { - this._replaceHistoryDelayer.trigger(() => this.replaceInput.addToHistory(this.replaceInput.value)); + this._replaceHistoryDelayer.trigger(() => this.replaceInput.addToHistory()); })); this.searchInputFocusTracker = this._register(dom.trackFocus(this.searchInput.inputBox.inputElement)); @@ -288,7 +288,7 @@ export class SearchWidget extends Widget { if (!this.ignoreGlobalFindBufferOnNextFocus && useGlobalFindBuffer) { const globalBufferText = this.clipboardServce.readFindText(); if (this.previousGlobalFindBufferValue !== globalBufferText) { - this.searchInput.inputBox.addToHistory(this.searchInput.getValue()); + this.searchInput.inputBox.addToHistory(); this.searchInput.setValue(globalBufferText); this.searchInput.select(); } From 9f05b498ca02a5d2cb660933b3157b35b308470c Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 27 Jun 2018 10:28:29 -0700 Subject: [PATCH 154/228] Fix js doc colorization Fixes #53162 --- extensions/theme-quietlight/themes/quietlight-color-theme.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/theme-quietlight/themes/quietlight-color-theme.json b/extensions/theme-quietlight/themes/quietlight-color-theme.json index 3e1ad56f8ad..e8915e04b78 100644 --- a/extensions/theme-quietlight/themes/quietlight-color-theme.json +++ b/extensions/theme-quietlight/themes/quietlight-color-theme.json @@ -40,7 +40,8 @@ "name": "Comments: Documentation", "scope": [ "comment.documentation", - "comment.block.documentation" + "comment.block.documentation", + "comment.block.documentation punctuation.definition.comment " ], "settings": { "foreground": "#448C27" From 8667e4ee1e4a8b4bac57a7024adebb3ed0d7fe7c Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 27 Jun 2018 20:37:18 +0200 Subject: [PATCH 155/228] Set extension only if exists --- .../parts/extensions/electron-browser/extensionsActions.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts index 66363c9a99b..c9ab1d72705 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts @@ -497,7 +497,11 @@ export class MultiServerInstallAction extends Action { this.actions = this.extensionManagementServerService.extensionManagementServers.map(server => this.instantiationService.createInstance(InstallGalleryExtensionAction, `extensions.install.${server.location.authority}`, localize('installInServer', "{0}", server.location.authority), server)); this._actionItem = this.instantiationService.createInstance(DropDownMenuActionItem, this, [this.actions]); this.disposables.push(...[this._actionItem, ...this.actions]); - this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.extension = this.extension ? this.extensionsWorkbenchService.local.filter(l => areSameExtensions({ id: l.id }, { id: this.extension.id }))[0] : this.extension)); + this.disposables.push(this.extensionsWorkbenchService.onChange(() => { + if (this.extension) { + this.extension = this.extensionsWorkbenchService.local.filter(l => areSameExtensions({ id: l.id }, { id: this.extension.id }))[0] || this.extension; + } + })); this.update(); } From 0b2dc7d3841e4d21b28e5ce24d67400f7299035f Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 27 Jun 2018 20:57:04 +0200 Subject: [PATCH 156/228] Update labels first --- .../electron-browser/extensionsActions.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts index c9ab1d72705..ff70baafa46 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts @@ -513,6 +513,16 @@ export class MultiServerInstallAction extends Action { return; } + if (this.extension.state === ExtensionState.Installing) { + this.label = MultiServerInstallAction.InstallingLabel; + this.class = MultiServerInstallAction.InstallingClass; + this.tooltip = MultiServerInstallAction.InstallingLabel; + } else { + this.label = MultiServerInstallAction.InstallLabel; + this.class = MultiServerInstallAction.Class; + this.tooltip = MultiServerInstallAction.InstallLabel; + } + const isInstalled = this.extension.locals.length > 0; if (isInstalled && this.disableWhenInstalled) { @@ -535,16 +545,6 @@ export class MultiServerInstallAction extends Action { }); this.enabled = this.extensionsWorkbenchService.canInstall(this.extension) && (isExtensionNotInstalledInRecommendedServer || this.extension.locals.length === 0); - - if (this.extension.state === ExtensionState.Installing) { - this.label = MultiServerInstallAction.InstallingLabel; - this.class = MultiServerInstallAction.InstallingClass; - this.tooltip = MultiServerInstallAction.InstallingLabel; - } else { - this.label = MultiServerInstallAction.InstallLabel; - this.class = MultiServerInstallAction.Class; - this.tooltip = MultiServerInstallAction.InstallLabel; - } } public run(): TPromise { From 32cee4ff9f8e6c0a6d24add5472b7e15a7a22902 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 27 Jun 2018 12:14:47 -0700 Subject: [PATCH 157/228] Fix #53123 - Fix "show modified only" for workspace setting --- .../parts/preferences/browser/settingsTree.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/parts/preferences/browser/settingsTree.ts b/src/vs/workbench/parts/preferences/browser/settingsTree.ts index 429b2628bc9..5d7a01d3f17 100644 --- a/src/vs/workbench/parts/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/parts/preferences/browser/settingsTree.ts @@ -772,7 +772,6 @@ function escapeInvisibleChars(enumValue: string): string { export class SettingsTreeFilter implements IFilter { constructor( private viewState: ISettingsEditorViewState, - @IConfigurationService private configurationService: IConfigurationService ) { } isVisible(tree: ITree, element: SettingsTreeElement): boolean { @@ -808,13 +807,12 @@ export class SettingsTreeFilter implements IFilter { private groupHasConfiguredSetting(element: SettingsTreeGroupElement): boolean { for (let child of element.children) { if (child instanceof SettingsTreeSettingElement) { - const { isConfigured } = inspectSetting(child.setting.key, this.viewState.settingsTarget, this.configurationService); - if (isConfigured) { + if (child.isConfigured) { return true; } - } else { - if (child instanceof SettingsTreeGroupElement) { - return this.groupHasConfiguredSetting(child); + } else if (child instanceof SettingsTreeGroupElement) { + if (this.groupHasConfiguredSetting(child)) { + return true; } } } From d15094602b4e347b593fc99f6968b807f3f7f810 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 27 Jun 2018 21:36:26 +0200 Subject: [PATCH 158/228] fix label --- .../parts/extensions/electron-browser/extensionsActions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts index ff70baafa46..087b2553530 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts @@ -448,7 +448,7 @@ export class UpdateGalleryExtensionAction extends Action { private update(): void { this.enabled = this.local && this.gallery && this.local.type === LocalExtensionType.User && semver.gt(this.gallery.version, this.local.manifest.version); - this.label = this.enabled ? localize('updateToInServer', "Update to {0} ({1})", this.local.manifest.version, this.server.location.authority) : localize('updateLabelInServer', "Update ({0})", this.server.location.authority); + this.label = this.enabled ? localize('updateToInServer', "Update to {0} ({1})", this.gallery.version, this.server.location.authority) : localize('updateLabelInServer', "Update ({0})", this.server.location.authority); } run(): TPromise { From 4af9901e419b3038ba74dafc021797cee6319ffe Mon Sep 17 00:00:00 2001 From: Rachel Macfarlane Date: Wed, 27 Jun 2018 13:16:49 -0700 Subject: [PATCH 159/228] Include cpuUsage.sh in out, fixes #53166 --- build/gulpfile.vscode.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index bf2baa6185e..88a57cb08e5 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -71,7 +71,7 @@ const vscodeResources = [ 'out-build/paths.js', 'out-build/vs/**/*.{svg,png,cur,html}', 'out-build/vs/base/common/performance.js', - 'out-build/vs/base/node/{stdForkStart.js,terminateProcess.sh, cpuUsage.sh}', + 'out-build/vs/base/node/{stdForkStart.js,terminateProcess.sh,cpuUsage.sh}', 'out-build/vs/base/browser/ui/octiconLabel/octicons/**', 'out-build/vs/workbench/browser/media/*-theme.css', 'out-build/vs/workbench/electron-browser/bootstrap/**', From 9769d31a3903f5410a39fffe7800a554ac4a5193 Mon Sep 17 00:00:00 2001 From: Andre Weinand Date: Wed, 27 Jun 2018 22:52:36 +0200 Subject: [PATCH 160/228] support var substitution for multiroot workspace --- .../mainThreadDebugService.ts | 2 +- .../workbench/api/node/extHostDebugService.ts | 21 +++++++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/api/electron-browser/mainThreadDebugService.ts b/src/vs/workbench/api/electron-browser/mainThreadDebugService.ts index f2159515af5..b98563c70b1 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadDebugService.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadDebugService.ts @@ -69,7 +69,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb } substituteVariables(folder: IWorkspaceFolder, config: IConfig): TPromise { - return this._proxy.$substituteVariables(folder.uri, config); + return this._proxy.$substituteVariables(folder ? folder.uri : undefined, config); } runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): TPromise { diff --git a/src/vs/workbench/api/node/extHostDebugService.ts b/src/vs/workbench/api/node/extHostDebugService.ts index d01e7dc7533..041ab383c13 100644 --- a/src/vs/workbench/api/node/extHostDebugService.ts +++ b/src/vs/workbench/api/node/extHostDebugService.ts @@ -166,15 +166,18 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { if (!this._variableResolver) { this._variableResolver = new ExtHostVariableResolverService(this._workspaceService, this._editorsService, this._configurationService); } + let ws: IWorkspaceFolder; const folder = this.getFolder(folderUri); - let ws: IWorkspaceFolder = { - uri: folder.uri, - name: folder.name, - index: folder.index, - toResource: () => { - throw new Error('Not implemented'); - } - }; + if (folder) { + ws = { + uri: folder.uri, + name: folder.name, + index: folder.index, + toResource: () => { + throw new Error('Not implemented'); + } + }; + } return asWinJsPromise(token => this._variableResolver.resolveAny(ws, config)); } @@ -524,7 +527,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { this._onDidReceiveDebugSessionCustomEvent.fire(ee); } - private getFolder(_folderUri: UriComponents | undefined): vscode.WorkspaceFolder { + private getFolder(_folderUri: UriComponents | undefined): vscode.WorkspaceFolder | undefined { if (_folderUri) { const folderURI = URI.revive(_folderUri); return this._workspaceService.resolveWorkspaceFolder(folderURI); From dcc3bec18294bbb9e65e42aa51a616b990a8e092 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 27 Jun 2018 14:16:35 -0700 Subject: [PATCH 161/228] Ensure ext host is told about first active terminal Fixes #53107 --- .../api/electron-browser/mainThreadTerminalService.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/workbench/api/electron-browser/mainThreadTerminalService.ts b/src/vs/workbench/api/electron-browser/mainThreadTerminalService.ts index 421daa0c9e4..242091da9b1 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadTerminalService.ts @@ -39,6 +39,10 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape this._onTerminalOpened(t); t.processReady.then(() => this._onTerminalProcessIdReady(t)); }); + const activeInstance = this.terminalService.getActiveInstance(); + if (activeInstance) { + this._proxy.$acceptActiveTerminalChanged(activeInstance.id); + } } public dispose(): void { From a1ad5a74ed5248d787aaaff1dcf47748a027a3b3 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 27 Jun 2018 14:45:29 -0700 Subject: [PATCH 162/228] Make TerminalRenderer.terminal synchronous Fixes #52934 --- src/vs/vscode.proposed.d.ts | 2 +- .../workbench/api/node/extHostTerminalService.ts | 16 +++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 5c7499cf43b..d1f859c29e7 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -412,7 +412,7 @@ declare module 'vscode' { /** * The corressponding [Terminal](#Terminal) for this TerminalRenderer. */ - readonly terminal: Thenable; + readonly terminal: Terminal; /** * Write text to the terminal. Unlike [Terminal.sendText](#Terminal.sendText) which sends diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index 4c16f048f99..f67e95e5a75 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -62,7 +62,7 @@ export class BaseExtHostTerminal { request.run(this._proxy, this._id); } - protected _runQueuedRequests(id: number): void { + public _runQueuedRequests(id: number): void { this._id = id; this._idPromiseComplete(id); this._queuedRequests.forEach((r) => { @@ -188,18 +188,19 @@ export class ExtHostTerminalRenderer extends BaseExtHostTerminal implements vsco return this._onDidChangeMaximumDimensions && this._onDidChangeMaximumDimensions.event; } - public get terminal(): Promise { - return this._idPromise.then(id => this._fetchTerminal(id)); + public get terminal(): ExtHostTerminal { + return this._terminal; } constructor( proxy: MainThreadTerminalServiceShape, private _name: string, - private _fetchTerminal: (id: number) => Promise + private _terminal: ExtHostTerminal ) { super(proxy); this._proxy.$createTerminalRenderer(this._name).then(id => { this._runQueuedRequests(id); + (this._terminal)._runQueuedRequests(id); }); } @@ -258,8 +259,13 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { } public createTerminalRenderer(name: string): vscode.TerminalRenderer { - const renderer = new ExtHostTerminalRenderer(this._proxy, name, (id) => this._getTerminalByIdEventually(id)); + const terminal = new ExtHostTerminal(this._proxy, name); + terminal._setProcessId(undefined); + this._terminals.push(terminal); + + const renderer = new ExtHostTerminalRenderer(this._proxy, name, terminal); this._terminalRenderers.push(renderer); + return renderer; } From fb6eb1465c44ee2ace0124c59300683df203ebe7 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 27 Jun 2018 14:58:29 -0700 Subject: [PATCH 163/228] Ensure maximumDimensions have changes before firing onDidChangeMaximumDimensions Fixes #52916 --- src/vs/workbench/api/node/extHostTerminalService.ts | 3 +++ .../parts/terminal/electron-browser/terminalInstance.ts | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index f67e95e5a75..2f677bef140 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -214,6 +214,9 @@ export class ExtHostTerminalRenderer extends BaseExtHostTerminal implements vsco } public _setMaximumDimensions(columns: number, rows: number): void { + if (this._maximumDimensions && this._maximumDimensions.columns === columns && this._maximumDimensions.rows === rows) { + return; + } this._maximumDimensions = { columns, rows }; this._onDidChangeMaximumDimensions.fire(this.maximumDimensions); } diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts index 6d9978c6211..1a73d38dbd7 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts @@ -963,7 +963,7 @@ export class TerminalInstance implements ITerminalInstance { this._safeSetOption('drawBoldTextInBrightColors', config.drawBoldTextInBrightColors); } - if (cols !== this._xterm.getOption('cols') || rows !== this._xterm.getOption('rows')) { + if (cols !== this._xterm.cols || rows !== this._xterm.rows) { this._onDimensionsChanged.fire(); } From 57ba99406e9dc40691c1b241864d88be99695f05 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Wed, 27 Jun 2018 14:59:56 -0700 Subject: [PATCH 164/228] Simplify recommendations (#53178) * Simplify recommendations * for ... of => forEach * Use unpacking --- .../common/extensionManagement.ts | 2 +- .../electron-browser/extensionEditor.ts | 9 +- .../electron-browser/extensionTipsService.ts | 252 +++++++----------- 3 files changed, 101 insertions(+), 162 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 16d69ec9f5b..25e0dcdb540 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -410,8 +410,8 @@ export interface IExtensionTipsService { getKeymapRecommendations(): IExtensionRecommendation[]; getAllRecommendations(): TPromise; getKeywordsForExtension(extension: string): string[]; - getRecommendationsForExtension(extension: string): string[]; toggleIgnoredRecommendation(extensionId: string, shouldIgnore: boolean): void; + getAllIgnoredRecommendations(): { global: string[], workspace: string[] }; onRecommendationChange: Event; } diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts index 3018ddd098f..05e6b948207 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts @@ -307,14 +307,19 @@ export class ExtensionEditor extends BaseEditor { this.description.textContent = extension.description; removeClass(this.header, 'recommendation-ignored'); + removeClass(this.header, 'recommended'); + const extRecommendations = this.extensionTipsService.getAllRecommendationsWithReason(); let recommendationsData = {}; if (extRecommendations[extension.id.toLowerCase()]) { addClass(this.header, 'recommended'); this.recommendationText.textContent = extRecommendations[extension.id.toLowerCase()].reasonText; recommendationsData = { recommendationReason: extRecommendations[extension.id.toLowerCase()].reasonId }; - } else { - removeClass(this.header, 'recommended'); + } else if (this.extensionTipsService.getAllIgnoredRecommendations().global.indexOf(extension.id.toLowerCase()) !== -1) { + addClass(this.header, 'recommendation-ignored'); + this.recommendationText.textContent = localize('recommendationHasBeenIgnored', "You have chosen not to receive recommendations for this extension."); + } + else { this.recommendationText.textContent = ''; } diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts index 37073609423..0713e7c1ecb 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts @@ -44,7 +44,6 @@ import URI from 'vs/base/common/uri'; import { areSameExtensions, getGalleryExtensionIdFromLocal } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IExperimentService, ExperimentActionType } from 'vs/workbench/parts/experiments/node/experimentService'; -const empty: { [key: string]: any; } = Object.create(null); const milliSecondsInADay = 1000 * 60 * 60 * 24; const choiceNever = localize('neverShowAgain', "Don't Show Again"); const searchMarketplace = localize('searchMarketplace', "Search Marketplace"); @@ -87,8 +86,6 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe private readonly _onRecommendationChange: Emitter = new Emitter(); onRecommendationChange: Event = this._onRecommendationChange.event; - private _sessionIgnoredRecommendations: { [id: string]: { reasonId: ExtensionRecommendationReason, reasonText: string, sources: ExtensionRecommendationSource[] } } = {}; - private _sessionRestoredRecommendations: { [id: string]: { reasonId: ExtensionRecommendationReason, reasonText: string, sources: ExtensionRecommendationSource[] } } = {}; constructor( @IExtensionGalleryService private readonly _galleryService: IExtensionGalleryService, @@ -111,6 +108,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe ) { super(); + if (!this.isEnabled()) { return; } @@ -125,13 +123,13 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe this.loadRecommendationsPromise = this.getWorkspaceRecommendations() .then(() => { // these must be called after workspace configs have been refreshed. - this.getCachedDynamicWorkspaceRecommendations(); - this._suggestFileBasedRecommendations(); - this.getExperimentalRecommendations(); - return this._suggestWorkspaceRecommendations(); + this.fetchCachedDynamicWorkspaceRecommendations(); + this.fetchFileBasedRecommendations(); + this.fetchExperimentalRecommendations(); + return this.promptWorkspaceRecommendations(); }).then(() => { - this._modelService.onModelAdded(this._suggest, this, this._disposables); - this._modelService.getModels().forEach(model => this._suggest(model)); + this._modelService.onModelAdded(this.promptFiletypeBasedRecommendations, this, this._disposables); + this._modelService.getModels().forEach(model => this.promptFiletypeBasedRecommendations(model)); }); if (!this.configurationService.getValue(ShowRecommendationsOnlyOnDemandKey)) { @@ -173,7 +171,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe fetchPromise = new TPromise((c, e) => { setTimeout(() => { - TPromise.join([this._suggestBasedOnExecutables(), this.getDynamicWorkspaceRecommendations()]).then(() => c(null)); + TPromise.join([this.fetchExecutableRecommendations(), this.fetchDynamicWorkspaceRecommendations()]).then(() => c(null)); }, calledDuringStartup ? 10000 : 0); }); @@ -199,7 +197,8 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe if (this.contextService.getWorkspace().folders && this.contextService.getWorkspace().folders.length === 1) { const currentRepo = this.contextService.getWorkspace().folders[0].name; - this._dynamicWorkspaceRecommendations.forEach(x => output[x.toLowerCase()] = { + + this._dynamicWorkspaceRecommendations.forEach(id => output[id.toLowerCase()] = { reasonId: ExtensionRecommendationReason.DynamicWorkspace, reasonText: localize('dynamicWorkspaceRecommendation', "This extension may interest you because it's popular among users of the {0} repository.", currentRepo) }); @@ -210,7 +209,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe reasonText: localize('exeBasedRecommendation', "This extension is recommended because you have {0} installed.", entry.value) }); - Object.keys(this._fileBasedRecommendations).forEach(x => output[x.toLowerCase()] = { + forEach(this._fileBasedRecommendations, entry => output[entry.key.toLowerCase()] = { reasonId: ExtensionRecommendationReason.File, reasonText: localize('fileBasedRecommendation', "This extension is recommended based on the files you recently opened.") }); @@ -220,22 +219,27 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe reasonText: localize('workspaceRecommendation', "This extension is recommended by users of the current workspace.") }); - Object.keys(this._sessionRestoredRecommendations).forEach(x => output[x.toLowerCase()] = { - reasonId: this._sessionRestoredRecommendations[x].reasonId, - reasonText: this._sessionRestoredRecommendations[x].reasonText - }); + for (const id of this._allIgnoredRecommendations) { + delete output[id]; + } return output; } + getAllIgnoredRecommendations(): { global: string[], workspace: string[] } { + return { + global: this._globallyIgnoredRecommendations, + workspace: this._workspaceIgnoredRecommendations + }; + } + getWorkspaceRecommendations(): TPromise { if (!this.isEnabled()) { return TPromise.as([]); } - return this.fetchWorkspaceRecommendations().then(() => this._allWorkspaceRecommendedExtensions); + return this.fetchWorkspaceRecommendations() + .then(() => this._allWorkspaceRecommendedExtensions.filter(rec => this.isExtensionAllowedToBeRecommended(rec.extensionId))); } private fetchWorkspaceRecommendations(): TPromise { - this._workspaceIgnoredRecommendations = []; - const tmpAllWorkspaceRecommendations = []; if (!this.isEnabled) { return TPromise.as(null); } @@ -249,6 +253,9 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe const seenUnWantedRecommendations: { [id: string]: boolean } = {}; + this._allWorkspaceRecommendedExtensions = []; + this._workspaceIgnoredRecommendations = []; + for (const contentsBySource of result) { if (contentsBySource.contents.unwantedRecommendations) { for (const r of contentsBySource.contents.unwantedRecommendations) { @@ -264,10 +271,10 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe for (const r of contentsBySource.contents.recommendations) { const extensionId = r.toLowerCase(); if (invalidExtensions.indexOf(extensionId) === -1) { - let recommendation = tmpAllWorkspaceRecommendations.filter(r => r.extensionId === extensionId)[0]; + let recommendation = this._allWorkspaceRecommendedExtensions.filter(r => r.extensionId === extensionId)[0]; if (!recommendation) { recommendation = { extensionId, sources: [] }; - tmpAllWorkspaceRecommendations.push(recommendation); + this._allWorkspaceRecommendedExtensions.push(recommendation); } if (recommendation.sources.indexOf(contentsBySource.source) === -1) { recommendation.sources.push(contentsBySource.source); @@ -276,9 +283,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe } } } - this._allWorkspaceRecommendedExtensions = tmpAllWorkspaceRecommendations; this._allIgnoredRecommendations = distinct([...this._globallyIgnoredRecommendations, ...this._workspaceIgnoredRecommendations]); - this.refilterAllRecommendations(); })); } @@ -358,24 +363,6 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe }); } - private refilterAllRecommendations() { - this._allWorkspaceRecommendedExtensions = this._allWorkspaceRecommendedExtensions.filter(({ extensionId }) => this.isExtensionAllowedToBeRecommended(extensionId)); - this._dynamicWorkspaceRecommendations = this._dynamicWorkspaceRecommendations.filter((id) => this.isExtensionAllowedToBeRecommended(id)); - - this._allIgnoredRecommendations.forEach(x => { - delete this._fileBasedRecommendations[x]; - delete this._exeBasedRecommendations[x]; - }); - - if (this._availableRecommendations) { - for (const key in this._availableRecommendations) { - if (Object.prototype.hasOwnProperty.call(this._availableRecommendations, key)) { - this._availableRecommendations[key] = this._availableRecommendations[key].filter(id => this.isExtensionAllowedToBeRecommended(id)); - } - } - } - } - private isExtensionAllowedToBeRecommended(id: string): boolean { return this._allIgnoredRecommendations.indexOf(id.toLowerCase()) === -1; } @@ -387,7 +374,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe .then(currentWorkspaceRecommended => { // Suggest only if at least one of the newly added recommendations was not suggested before if (currentWorkspaceRecommended.some(current => oldWorkspaceRecommended.every(old => current.extensionId !== old.extensionId))) { - this._suggestWorkspaceRecommendations(); + this.promptWorkspaceRecommendations(); } }); } @@ -395,24 +382,20 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe } getFileBasedRecommendations(): IExtensionRecommendation[] { - return [...this.getRestoredRecommendationsByReason(ExtensionRecommendationReason.File), - ...Object.keys(this._fileBasedRecommendations).sort((a, b) => { - if (this._fileBasedRecommendations[a].recommendedTime === this._fileBasedRecommendations[b].recommendedTime) { - if (!product.extensionImportantTips || caseInsensitiveGet(product.extensionImportantTips, a)) { - return -1; + return Object.keys(this._fileBasedRecommendations) + .sort((a, b) => { + if (this._fileBasedRecommendations[a].recommendedTime === this._fileBasedRecommendations[b].recommendedTime) { + if (!product.extensionImportantTips || caseInsensitiveGet(product.extensionImportantTips, a)) { + return -1; + } + if (caseInsensitiveGet(product.extensionImportantTips, b)) { + return 1; + } } - if (caseInsensitiveGet(product.extensionImportantTips, b)) { - return 1; - } - } - return this._fileBasedRecommendations[a].recommendedTime > this._fileBasedRecommendations[b].recommendedTime ? -1 : 1; - })] - .map(extensionId => ({ - extensionId, sources: - this._fileBasedRecommendations[extensionId] ? this._fileBasedRecommendations[extensionId].sources : - this._sessionRestoredRecommendations[extensionId.toLowerCase()] ? this._sessionRestoredRecommendations[extensionId.toLowerCase()].sources : - [] - })); + return this._fileBasedRecommendations[a].recommendedTime > this._fileBasedRecommendations[b].recommendedTime ? -1 : 1; + }) + .filter(extensionId => this.isExtensionAllowedToBeRecommended(extensionId)) + .map(extensionId => ({ extensionId, sources: this._fileBasedRecommendations[extensionId].sources })); } getOtherRecommendations(): TPromise { @@ -421,35 +404,25 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe ...Object.keys(this._exeBasedRecommendations), ...this._dynamicWorkspaceRecommendations, ...Object.keys(this._experimentalRecommendations), - ]); + ]).filter(extensionId => this.isExtensionAllowedToBeRecommended(extensionId)); shuffle(others); - return [ - ...this.getRestoredRecommendationsByReason(ExtensionRecommendationReason.Executable), - ...this.getRestoredRecommendationsByReason(ExtensionRecommendationReason.DynamicWorkspace), - ...this.getRestoredRecommendationsByReason(ExtensionRecommendationReason.Experimental), - ...others] - .map(extensionId => { - const sources: ExtensionRecommendationSource[] = []; - if (this._exeBasedRecommendations[extensionId]) { - sources.push('executable'); - } - if (this._dynamicWorkspaceRecommendations[extensionId]) { - sources.push('dynamic'); - } - if (this._sessionRestoredRecommendations[extensionId.toLowerCase()]) { - sources.push(...this._sessionRestoredRecommendations[extensionId.toLowerCase()].sources); - } - return ({ extensionId, sources }); - }); + return others.map(extensionId => { + const sources: ExtensionRecommendationSource[] = []; + if (this._exeBasedRecommendations[extensionId]) { + sources.push('executable'); + } + if (this._dynamicWorkspaceRecommendations[extensionId]) { + sources.push('dynamic'); + } + return ({ extensionId, sources }); + }); }); } - private getRestoredRecommendationsByReason(reason: ExtensionRecommendationReason): string[] { - return Object.keys(this._sessionRestoredRecommendations).filter(key => this._sessionRestoredRecommendations[key].reasonId === reason); - } - getKeymapRecommendations(): IExtensionRecommendation[] { - return (product.keymapExtensionTips || []).map(extensionId => ({ extensionId, sources: ['application'] })); + return (product.keymapExtensionTips || []) + .filter(extensionId => this.isExtensionAllowedToBeRecommended(extensionId)) + .map(extensionId => ({ extensionId, sources: ['application'] })); } getAllRecommendations(): TPromise { @@ -461,7 +434,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe ]).then(result => flatten(result)); } - private _suggestFileBasedRecommendations() { + private fetchFileBasedRecommendations() { const extensionTips = product.extensionTips; if (!extensionTips) { return; @@ -471,26 +444,22 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe this._availableRecommendations = Object.create(null); forEach(extensionTips, entry => { let { key: id, value: pattern } = entry; - if (this.isExtensionAllowedToBeRecommended(id)) { - let ids = this._availableRecommendations[pattern]; - if (!ids) { - this._availableRecommendations[pattern] = [id.toLowerCase()]; - } else { - ids.push(id.toLowerCase()); - } + let ids = this._availableRecommendations[pattern]; + if (!ids) { + this._availableRecommendations[pattern] = [id.toLowerCase()]; + } else { + ids.push(id.toLowerCase()); } }); forEach(product.extensionImportantTips, entry => { let { key: id, value } = entry; - if (this.isExtensionAllowedToBeRecommended(id)) { - const { pattern } = value; - let ids = this._availableRecommendations[pattern]; - if (!ids) { - this._availableRecommendations[pattern] = [id.toLowerCase()]; - } else { - ids.push(id.toLowerCase()); - } + const { pattern } = value; + let ids = this._availableRecommendations[pattern]; + if (!ids) { + this._availableRecommendations[pattern] = [id.toLowerCase()]; + } else { + ids.push(id.toLowerCase()); } }); @@ -524,10 +493,10 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe }); } - private _suggest(model: ITextModel): void { - const uri = model.uri; + private promptFiletypeBasedRecommendations(model: ITextModel): void { let hasSuggestion = false; + const uri = model.uri; if (!uri) { return; } @@ -574,7 +543,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe } const importantRecommendationsIgnoreList = JSON.parse(this.storageService.get('extensionsAssistant/importantRecommendationsIgnore', StorageScope.GLOBAL, '[]')); - recommendationsToSuggest = recommendationsToSuggest.filter(id => importantRecommendationsIgnoreList.indexOf(id) === -1); + recommendationsToSuggest = recommendationsToSuggest.filter(id => importantRecommendationsIgnoreList.indexOf(id) === -1 && this.isExtensionAllowedToBeRecommended(id)); const server = this.extensionManagementServiceService.getExtensionManagementServer(model.uri); const importantTipsPromise = recommendationsToSuggest.length === 0 ? TPromise.as(null) : server.extensionManagementService.getInstalled(LocalExtensionType.User).then(local => { @@ -642,7 +611,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe } */ this.telemetryService.publicLog('extensionRecommendations:popup', { userReaction: 'neverShowAgain', extensionId: name }); - this.ignoreExtensionRecommendations(); + this.promptIgnoreExtensionRecommendations(); } }], () => { @@ -737,17 +706,20 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe }); } - private _suggestWorkspaceRecommendations(): void { + private promptWorkspaceRecommendations(): void { const storageKey = 'extensionsAssistant/workspaceRecommendationsIgnore'; const config = this.configurationService.getValue(ConfigurationKey); + const filteredRecs = this._allWorkspaceRecommendedExtensions.filter(rec => this.isExtensionAllowedToBeRecommended(rec.extensionId)); - if (!this._allWorkspaceRecommendedExtensions.length || config.ignoreRecommendations || config.showRecommendationsOnlyOnDemand || this.storageService.getBoolean(storageKey, StorageScope.WORKSPACE, false)) { + if (filteredRecs.length === 0 + || config.ignoreRecommendations + || config.showRecommendationsOnlyOnDemand + || this.storageService.getBoolean(storageKey, StorageScope.WORKSPACE, false)) { return; } return this.extensionsService.getInstalled(LocalExtensionType.User).done(local => { - const recommendations = this._allWorkspaceRecommendedExtensions - .filter(({ extensionId }) => local.every(local => !areSameExtensions({ id: extensionId }, { id: getGalleryExtensionIdFromLocal(local) }))); + const recommendations = filteredRecs.filter(({ extensionId }) => local.every(local => !areSameExtensions({ id: extensionId }, { id: getGalleryExtensionIdFromLocal(local) }))); if (!recommendations.length) { return TPromise.as(void 0); @@ -819,7 +791,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe }); } - private ignoreExtensionRecommendations() { + private promptIgnoreExtensionRecommendations() { this.notificationService.prompt( Severity.Info, localize('ignoreExtensionRecommendations', "Do you want to ignore all extension recommendations?"), @@ -833,7 +805,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe ); } - private _suggestBasedOnExecutables(): TPromise { + private fetchExecutableRecommendations(): TPromise { const homeDir = os.homedir(); let foundExecutables: Set = new Set(); @@ -843,7 +815,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe foundExecutables.add(exeName); (product.exeBasedExtensionTips[exeName]['recommendations'] || []) .forEach(extensionId => { - if (product.exeBasedExtensionTips[exeName]['friendlyName'] && this.isExtensionAllowedToBeRecommended(extensionId)) { + if (product.exeBasedExtensionTips[exeName]['friendlyName']) { this._exeBasedRecommendations[extensionId.toLowerCase()] = product.exeBasedExtensionTips[exeName]['friendlyName']; } }); @@ -886,7 +858,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe } } - private getCachedDynamicWorkspaceRecommendations() { + private fetchCachedDynamicWorkspaceRecommendations() { if (this.contextService.getWorkbenchState() !== WorkbenchState.FOLDER) { return; } @@ -903,7 +875,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe && isNumber(storedRecommendationsJson['timestamp']) && storedRecommendationsJson['timestamp'] > 0 && (Date.now() - storedRecommendationsJson['timestamp']) / milliSecondsInADay < 14) { - this._dynamicWorkspaceRecommendations = storedRecommendationsJson['recommendations'].filter(id => this.isExtensionAllowedToBeRecommended(id)); + this._dynamicWorkspaceRecommendations = storedRecommendationsJson['recommendations']; /* __GDPR__ "dynamicWorkspaceRecommendations" : { "count" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, @@ -914,7 +886,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe } } - private getDynamicWorkspaceRecommendations(): TPromise { + private fetchDynamicWorkspaceRecommendations(): TPromise { if (this.contextService.getWorkbenchState() !== WorkbenchState.FOLDER || this._dynamicWorkspaceRecommendations.length || !this._extensionsRecommendationsUrl) { @@ -964,14 +936,12 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe }); } - private getExperimentalRecommendations() { + private fetchExperimentalRecommendations() { this.experimentService.getExperimentsToRunByType(ExperimentActionType.AddToRecommendations).then(experiments => { (experiments || []).forEach(experiment => { if (experiment.action.properties && Array.isArray(experiment.action.properties.recommendations) && experiment.action.properties.recommendationReason) { experiment.action.properties.recommendations.forEach(id => { - if (this.isExtensionAllowedToBeRecommended(id)) { - this._experimentalRecommendations[id] = experiment.action.properties.recommendationReason; - } + this._experimentalRecommendations[id] = experiment.action.properties.recommendationReason; }); } }); @@ -983,30 +953,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe return keywords[extension] || []; } - getRecommendationsForExtension(extension: string): string[] { - const str = `.${extension}`; - const result = Object.create(null); - - forEach(product.extensionTips || empty, entry => { - let { key: id, value: pattern } = entry; - - if (match(pattern, str)) { - result[id] = true; - } - }); - - forEach(product.extensionImportantTips || empty, entry => { - let { key: id, value } = entry; - - if (match(value.pattern, str)) { - result[id] = true; - } - }); - - return Object.keys(result); - } - - toggleIgnoredRecommendation(extensionId: string, shouldIgnore: boolean): void { + toggleIgnoredRecommendation(extensionId: string, shouldIgnore: boolean) { const lowerId = extensionId.toLowerCase(); if (shouldIgnore) { const reason = this.getAllRecommendationsWithReason()[lowerId]; @@ -1019,28 +966,15 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe */ this.telemetryService.publicLog('extensionsRecommendations:ignoreRecommendation', { id: extensionId, recommendationReason: reason.reasonId }); } - this._sessionIgnoredRecommendations[lowerId] = { - ...reason, sources: - coalesce([ - <'executable' | null>(caseInsensitiveGet(this._exeBasedRecommendations, lowerId) ? 'executable' : null), - ...(() => { let a = caseInsensitiveGet(this._fileBasedRecommendations, lowerId); return a ? a.sources : null; })(), - <'dynamic' | null>(this._dynamicWorkspaceRecommendations.filter(x => x.toLowerCase() === lowerId).length > 0 ? 'dynamic' : null), - ]) - }; - delete this._sessionRestoredRecommendations[lowerId]; - this._globallyIgnoredRecommendations = distinct([...this._globallyIgnoredRecommendations, lowerId].map(id => id.toLowerCase())); - } else { - this._globallyIgnoredRecommendations = this._globallyIgnoredRecommendations.filter(id => id !== lowerId); - if (this._sessionIgnoredRecommendations[lowerId]) { - this._sessionRestoredRecommendations[lowerId] = this._sessionIgnoredRecommendations[lowerId]; - delete this._sessionIgnoredRecommendations[lowerId]; - } } - this.storageService.store('extensionsAssistant/ignored_recommendations', JSON.stringify(this._globallyIgnoredRecommendations), StorageScope.GLOBAL); + this._globallyIgnoredRecommendations = shouldIgnore ? + distinct([...this._globallyIgnoredRecommendations, lowerId].map(id => id.toLowerCase())) : + this._globallyIgnoredRecommendations.filter(id => id !== lowerId); + + this.storageService.store('extensionsAssistant/ignored_recommendations', JSON.stringify(this._globallyIgnoredRecommendations), StorageScope.GLOBAL); this._allIgnoredRecommendations = distinct([...this._globallyIgnoredRecommendations, ...this._workspaceIgnoredRecommendations]); - this.refilterAllRecommendations(); this._onRecommendationChange.fire({ extensionId: extensionId, isRecommended: !shouldIgnore }); } From 4457b76175333879f32fbbedccbbe9f0f60a0f1d Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Wed, 27 Jun 2018 15:08:27 -0700 Subject: [PATCH 165/228] fixes #52909 --- src/vs/platform/windows/common/windows.ts | 1 + src/vs/platform/windows/common/windowsIpc.ts | 7 ++ .../windows/electron-main/windowsService.ts | 2 + .../browser/parts/menubar/menubarPart.ts | 92 ++++++++++++++++++- .../workbench/test/workbenchTestServices.ts | 1 + 5 files changed, 100 insertions(+), 3 deletions(-) diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index 8df44ae6f68..9c94e242e06 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -105,6 +105,7 @@ export interface IWindowsService { onWindowBlur: Event; onWindowMaximize: Event; onWindowUnmaximize: Event; + onRecentlyOpenedChange: Event; // Dialogs pickFileFolderAndOpen(options: INativeOpenDialogOptions): TPromise; diff --git a/src/vs/platform/windows/common/windowsIpc.ts b/src/vs/platform/windows/common/windowsIpc.ts index 3cc5e90f688..8875c105621 100644 --- a/src/vs/platform/windows/common/windowsIpc.ts +++ b/src/vs/platform/windows/common/windowsIpc.ts @@ -19,6 +19,7 @@ export interface IWindowsChannel extends IChannel { call(command: 'event:onWindowOpen'): TPromise; call(command: 'event:onWindowFocus'): TPromise; call(command: 'event:onWindowBlur'): TPromise; + call(command: 'event:onRecentlyOpenedChange'): TPromise; call(command: 'pickFileFolderAndOpen', arg: INativeOpenDialogOptions): TPromise; call(command: 'pickFileAndOpen', arg: INativeOpenDialogOptions): TPromise; call(command: 'pickFolderAndOpen', arg: INativeOpenDialogOptions): TPromise; @@ -77,6 +78,7 @@ export class WindowsChannel implements IWindowsChannel { private onWindowBlur: Event; private onWindowMaximize: Event; private onWindowUnmaximize: Event; + private onRecentlyOpenedChange: Event; constructor(private service: IWindowsService) { this.onWindowOpen = buffer(service.onWindowOpen, true); @@ -84,6 +86,7 @@ export class WindowsChannel implements IWindowsChannel { this.onWindowBlur = buffer(service.onWindowBlur, true); this.onWindowMaximize = buffer(service.onWindowMaximize, true); this.onWindowUnmaximize = buffer(service.onWindowUnmaximize, true); + this.onRecentlyOpenedChange = buffer(service.onRecentlyOpenedChange, true); } call(command: string, arg?: any): TPromise { @@ -93,6 +96,7 @@ export class WindowsChannel implements IWindowsChannel { case 'event:onWindowBlur': return eventToCall(this.onWindowBlur); case 'event:onWindowMaximize': return eventToCall(this.onWindowMaximize); case 'event:onWindowUnmaximize': return eventToCall(this.onWindowUnmaximize); + case 'event:onRecentlyOpenedChange': return eventToCall(this.onRecentlyOpenedChange); case 'pickFileFolderAndOpen': return this.service.pickFileFolderAndOpen(arg); case 'pickFileAndOpen': return this.service.pickFileAndOpen(arg); case 'pickFolderAndOpen': return this.service.pickFolderAndOpen(arg); @@ -181,6 +185,9 @@ export class WindowsChannelClient implements IWindowsService { private _onWindowUnmaximize: Event = eventFromCall(this.channel, 'event:onWindowUnmaximize'); get onWindowUnmaximize(): Event { return this._onWindowUnmaximize; } + private _onRecentlyOpenedChange: Event = eventFromCall(this.channel, 'event:onRecentlyOpenedChange'); + get onRecentlyOpenedChange(): Event { return this._onRecentlyOpenedChange; } + pickFileFolderAndOpen(options: INativeOpenDialogOptions): TPromise { return this.channel.call('pickFileFolderAndOpen', options); } diff --git a/src/vs/platform/windows/electron-main/windowsService.ts b/src/vs/platform/windows/electron-main/windowsService.ts index e9a6e08c684..44df06d1f9f 100644 --- a/src/vs/platform/windows/electron-main/windowsService.ts +++ b/src/vs/platform/windows/electron-main/windowsService.ts @@ -42,6 +42,8 @@ export class WindowsService implements IWindowsService, IURLHandler, IDisposable readonly onWindowMaximize: Event = filterEvent(fromNodeEventEmitter(app, 'browser-window-maximize', (_, w: Electron.BrowserWindow) => w.id), id => !!this.windowsMainService.getWindowById(id)); readonly onWindowUnmaximize: Event = filterEvent(fromNodeEventEmitter(app, 'browser-window-unmaximize', (_, w: Electron.BrowserWindow) => w.id), id => !!this.windowsMainService.getWindowById(id)); + readonly onRecentlyOpenedChange: Event = this.historyService.onRecentlyOpenedChange; + constructor( private sharedProcess: ISharedProcess, @IWindowsMainService private windowsMainService: IWindowsMainService, diff --git a/src/vs/workbench/browser/parts/menubar/menubarPart.ts b/src/vs/workbench/browser/parts/menubar/menubarPart.ts index 2f287de3d41..af50fa982b4 100644 --- a/src/vs/workbench/browser/parts/menubar/menubarPart.ts +++ b/src/vs/workbench/browser/parts/menubar/menubarPart.ts @@ -13,9 +13,9 @@ import { Part } from 'vs/workbench/browser/part'; import { IMenubarService, IMenubarMenu, IMenubarMenuItemAction, IMenubarData } from 'vs/platform/menubar/common/menubar'; import { IMenuService, MenuId, IMenu, SubmenuItemAction } from 'vs/platform/actions/common/actions'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IWindowService, MenuBarVisibility } from 'vs/platform/windows/common/windows'; +import { IWindowService, MenuBarVisibility, IWindowsService } from 'vs/platform/windows/common/windows'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { ActionRunner, IActionRunner, IAction } from 'vs/base/common/actions'; +import { ActionRunner, IActionRunner, IAction, Action } from 'vs/base/common/actions'; import { Builder, $ } from 'vs/base/browser/builder'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { EventType, Dimension, toggleClass } from 'vs/base/browser/dom'; @@ -30,6 +30,10 @@ import { Color } from 'vs/base/common/color'; import { Event, Emitter } from 'vs/base/common/event'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { domEvent } from 'vs/base/browser/event'; +import { IRecentlyOpened } from 'vs/platform/history/common/history'; +import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, getWorkspaceLabel } from 'vs/platform/workspaces/common/workspaces'; +import { getPathLabel } from 'vs/base/common/labels'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; interface CustomMenu { title: string; @@ -86,6 +90,7 @@ export class MenubarPart extends Part { private actionRunner: IActionRunner; private container: Builder; + private recentlyOpened: IRecentlyOpened; private _modifierKeyStatus: IModifierKeyStatus; private _isFocused: boolean; private _onVisibilityChange: Emitter; @@ -98,15 +103,19 @@ export class MenubarPart extends Part { menubarFontSize?: number; } = {}; + private static MAX_MENU_RECENT_ENTRIES = 5; + constructor( id: string, @IThemeService themeService: IThemeService, @IMenubarService private menubarService: IMenubarService, @IMenuService private menuService: IMenuService, @IWindowService private windowService: IWindowService, + @IWindowsService private windowsService: IWindowsService, @IContextKeyService private contextKeyService: IContextKeyService, @IKeybindingService private keybindingService: IKeybindingService, - @IConfigurationService private configurationService: IConfigurationService + @IConfigurationService private configurationService: IConfigurationService, + @IEnvironmentService private environmentService: IEnvironmentService ) { super(id, { hasTitle: false }, themeService); @@ -144,6 +153,10 @@ export class MenubarPart extends Part { this.isFocused = false; + this.windowService.getRecentlyOpened().then((recentlyOpened) => { + this.recentlyOpened = recentlyOpened; + }); + this.registerListeners(); } @@ -253,6 +266,13 @@ export class MenubarPart extends Part { } } + private onRecentlyOpenedChange(): void { + this.windowService.getRecentlyOpened().then(recentlyOpened => { + this.recentlyOpened = recentlyOpened; + this.setupMenubar(); + }); + } + private registerListeners(): void { browser.onDidChangeFullscreen(() => this.onDidChangeFullscreen()); @@ -262,6 +282,9 @@ export class MenubarPart extends Part { // Listen to update service // this.updateService.onStateChange(() => this.setupMenubar()); + // Listen for changes in recently opened menu + this.windowsService.onRecentlyOpenedChange(() => { this.onRecentlyOpenedChange(); }); + // Listen to keybindings change this.keybindingService.onDidUpdateKeybindings(() => this.setupMenubar()); @@ -342,6 +365,68 @@ export class MenubarPart extends Part { return this.currentEnableMenuBarMnemonics ? label : label.replace(/&&(.)/g, '$1'); } + private createOpenRecentMenuAction(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | string, commandId: string, isFile: boolean): IAction { + + let label: string; + let path: string; + + if (isSingleFolderWorkspaceIdentifier(workspace) || typeof workspace === 'string') { + label = getPathLabel(workspace, this.environmentService); + path = workspace; + } else { + label = getWorkspaceLabel(workspace, this.environmentService, { verbose: true }); + path = workspace.configPath; + } + + return new Action(commandId, label, undefined, undefined, (event) => { + const openInNewWindow = event && ((!isMacintosh && (event.ctrlKey || event.shiftKey)) || (isMacintosh && (event.metaKey || event.altKey))); + + return this.windowService.openWindow([path], { + forceNewWindow: openInNewWindow, + forceOpenWorkspaceAsFile: isFile + }); + }); + } + + private getOpenRecentActions(): IAction[] { + if (!this.recentlyOpened) { + return []; + } + + const { workspaces, files } = this.recentlyOpened; + + const result: IAction[] = []; + + if (workspaces.length > 0) { + for (let i = 0; i < MenubarPart.MAX_MENU_RECENT_ENTRIES && i < workspaces.length; i++) { + result.push(this.createOpenRecentMenuAction(workspaces[i], 'openRecentWorkspace', false)); + } + + result.push(new Separator()); + } + + if (files.length > 0) { + for (let i = 0; i < MenubarPart.MAX_MENU_RECENT_ENTRIES && i < files.length; i++) { + result.push(this.createOpenRecentMenuAction(files[i], 'openRecentFile', false)); + } + + result.push(new Separator()); + } + + return result; + } + + private insertActionsBefore(nextAction: IAction, target: IAction[]): void { + switch (nextAction.id) { + case 'workbench.action.openRecent': + target.push(...this.getOpenRecentActions()); + break; + + default: + break; + } + } + private setupCustomMenubar(): void { this.container.empty(); this.container.attr('role', 'menubar'); @@ -381,6 +466,7 @@ export class MenubarPart extends Part { const [, actions] = group; for (let action of actions) { + this.insertActionsBefore(action, target); if (action instanceof SubmenuItemAction) { const submenu = this.menuService.createMenu(action.item.submenu, this.contextKeyService); const submenuActions = []; diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index b9e2dabcb74..a64e270d6cc 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -1123,6 +1123,7 @@ export class TestWindowsService implements IWindowsService { onWindowBlur: Event; onWindowMaximize: Event; onWindowUnmaximize: Event; + onRecentlyOpenedChange: Event; isFocused(windowId: number): TPromise { return TPromise.as(false); From 8a4c0f23f02b8ceed91793086a50db1c657f5cd9 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Wed, 27 Jun 2018 15:24:32 -0700 Subject: [PATCH 166/228] fixes #52359 --- src/vs/workbench/browser/parts/menubar/media/menubarpart.css | 2 +- src/vs/workbench/electron-browser/media/shell.css | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/menubar/media/menubarpart.css b/src/vs/workbench/browser/parts/menubar/media/menubarpart.css index 1fb3533853f..b4a0416b18c 100644 --- a/src/vs/workbench/browser/parts/menubar/media/menubarpart.css +++ b/src/vs/workbench/browser/parts/menubar/media/menubarpart.css @@ -28,7 +28,7 @@ padding: 0px 5px; position: relative; z-index: 10; - cursor: pointer; + cursor: default; -webkit-app-region: no-drag; zoom: 1; } diff --git a/src/vs/workbench/electron-browser/media/shell.css b/src/vs/workbench/electron-browser/media/shell.css index 436641f4237..3b5709bfcac 100644 --- a/src/vs/workbench/electron-browser/media/shell.css +++ b/src/vs/workbench/electron-browser/media/shell.css @@ -78,6 +78,10 @@ padding: 0.5em 1em; } +.monaco-shell .monaco-menu .action-item { + cursor: default; +} + /* START Keyboard Focus Indication Styles */ .monaco-shell [tabindex="0"]:focus, From 10c00a466b9f246a14b2df0c5148586b85909c92 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Wed, 27 Jun 2018 15:43:18 -0700 Subject: [PATCH 167/228] Ignore Recommendation button on zoom/small width (#53079) * add better padding to undo/recommend button * Better supposrt zoom and small windows * Wrapping --- .../electron-browser/extensionEditor.ts | 2 +- .../electron-browser/media/extensionEditor.css | 18 +++++++----------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts index 05e6b948207..51fe81f799b 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts @@ -164,7 +164,7 @@ export class ExtensionEditor extends BaseEditor { private navbar: NavBar; private content: HTMLElement; private recommendation: HTMLElement; - private recommendationText: any; + private recommendationText: HTMLElement; private ignoreActionbar: ActionBar; private header: HTMLElement; diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/extensionEditor.css b/src/vs/workbench/parts/extensions/electron-browser/media/extensionEditor.css index de0b561410a..10efc9daede 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/media/extensionEditor.css +++ b/src/vs/workbench/parts/extensions/electron-browser/media/extensionEditor.css @@ -14,7 +14,6 @@ .extension-editor > .header { display: flex; - height: 134px; padding-top: 20px; padding-bottom: 14px; padding-left: 20px; @@ -23,12 +22,6 @@ font-size: 14px; } -.extension-editor > .header.recommended, -.extension-editor > .header.recommendation-ignored -{ - height: 146px; -} - .extension-editor > .header > .icon { height: 128px; width: 128px; @@ -36,7 +29,6 @@ } .extension-editor > .header > .details { - flex: 1; padding-left: 20px; overflow: hidden; user-select: text; @@ -139,26 +131,30 @@ .extension-editor > .header.recommended > .details > .recommendation, .extension-editor > .header.recommendation-ignored > .details > .recommendation { - display: flex; + display: block; + float: left; margin-top: 0; font-size: 13px; - height: 25px; font-style: italic; } .extension-editor > .header.recommended > .details > .recommendation > .monaco-action-bar, .extension-editor > .header.recommendation-ignored > .details > .recommendation > .monaco-action-bar { - margin-left: 4px; + float: left; margin-top: 2px; font-style: normal; } .extension-editor > .header > .details > .recommendation > .recommendation-text { + float:left; margin-top: 5px; + margin-right: 4px; } .extension-editor > .header > .details > .recommendation > .monaco-action-bar .action-label { margin-top: 4px; margin-left: 4px; + padding-top: 0; + padding-bottom: 2px; } .extension-editor > .header.recommendation-ignored > .details > .recommendation > .monaco-action-bar .ignore { From 2198155603cf9901292cb08a8baacd4675a36e6d Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 27 Jun 2018 15:43:19 -0700 Subject: [PATCH 168/228] Clairify webview serializer docs Fixes #53082 --- src/vs/vscode.d.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index ab02ccebbbe..10e2b712d23 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -5565,7 +5565,8 @@ declare module 'vscode' { * setState({ value: oldState.value + 1 }) * ``` * - * A `WebviewPanelSerializer` extends this persistence across restarts of VS Code. When the editor is shutdown, VS Code will save off the state from `setState` of all webviews that have a serializer. When the + * A `WebviewPanelSerializer` extends this persistence across restarts of VS Code. When the editor is shutdown, + * VS Code will save off the state from `setState` of all webviews that have a serializer. When the * webview first becomes visible after the restart, this state is passed to `deserializeWebviewPanel`. * The extension can then restore the old `WebviewPanel` from this state. */ @@ -5575,8 +5576,9 @@ declare module 'vscode' { * * Called when a serialized webview first becomes visible. * - * @param webviewPanel Webview panel to restore. The serializer should take ownership of this panel. - * @param state Persisted state. + * @param webviewPanel Webview panel to restore. The serializer should take ownership of this panel. The + * serializer must restore the webview's `.html` and hook up all webview events. + * @param state Persisted state from the webview content. * * @return Thanble indicating that the webview has been fully restored. */ @@ -6193,7 +6195,7 @@ declare module 'vscode' { /** * Registers a webview panel serializer. * - * Extensions that support reviving should have an `"onWebviewPanel:viewType"` activation method and + * Extensions that support reviving should have an `"onWebviewPanel:viewType"` activation event and * make sure that [registerWebviewPanelSerializer](#registerWebviewPanelSerializer) is called during activation. * * Only a single serializer may be registered at a time for a given `viewType`. From 6018fea28291708b1c10dad998418730309f06c4 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 27 Jun 2018 16:42:46 -0700 Subject: [PATCH 169/228] Fix #52508 - error when opening/closing old prefs editor quickly --- .../workbench/parts/preferences/browser/preferencesEditor.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts b/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts index 61669bfa5e3..fb3f595fa2e 100644 --- a/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts +++ b/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts @@ -832,7 +832,7 @@ class SideBySidePreferencesWidget extends Widget { this._register(focusTracker.onDidFocus(() => this._onFocus.fire())); } - public setInput(defaultPreferencesEditorInput: DefaultPreferencesEditorInput, editablePreferencesEditorInput: EditorInput, options: EditorOptions, token: CancellationToken): TPromise<{ defaultPreferencesRenderer: IPreferencesRenderer, editablePreferencesRenderer: IPreferencesRenderer }> { + public setInput(defaultPreferencesEditorInput: DefaultPreferencesEditorInput, editablePreferencesEditorInput: EditorInput, options: EditorOptions, token: CancellationToken): TPromise<{ defaultPreferencesRenderer?: IPreferencesRenderer, editablePreferencesRenderer?: IPreferencesRenderer }> { this.getOrCreateEditablePreferencesEditor(editablePreferencesEditorInput); this.settingsTargetsWidget.settingsTarget = this.getSettingsTarget(editablePreferencesEditorInput.getResource()); return TPromise.join([ @@ -841,7 +841,7 @@ class SideBySidePreferencesWidget extends Widget { ]) .then(([defaultPreferencesRenderer, editablePreferencesRenderer]) => { if (token.isCancellationRequested) { - return void 0; + return {}; } this.defaultPreferencesHeader.textContent = defaultPreferencesRenderer && this.getDefaultPreferencesHeaderText((defaultPreferencesRenderer.preferencesModel).target); From 47bd68917b829604275b6cc565aedb332ee6fae6 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 27 Jun 2018 16:54:59 -0700 Subject: [PATCH 170/228] Bump node2 with fix for #52953 --- build/builtInExtensions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/builtInExtensions.json b/build/builtInExtensions.json index ad7d8680ade..222ab55dce4 100644 --- a/build/builtInExtensions.json +++ b/build/builtInExtensions.json @@ -6,7 +6,7 @@ }, { "name": "ms-vscode.node-debug2", - "version": "1.25.4", + "version": "1.25.5", "repo": "https://github.com/Microsoft/vscode-node-debug2" } ] From 56fb08bd529544ac1eb9d89abd7a4af53bf508de Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Wed, 27 Jun 2018 16:58:11 -0700 Subject: [PATCH 171/228] Fix bug causing recommendation ignore message not going away (#53189) --- .../parts/extensions/electron-browser/extensionEditor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts index 51fe81f799b..f3a1da645e0 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts @@ -401,9 +401,9 @@ export class ExtensionEditor extends BaseEditor { this.extensionTipsService.onRecommendationChange(change => { if (change.extensionId.toLowerCase() === extension.id.toLowerCase()) { if (change.isRecommended) { + removeClass(this.header, 'recommendation-ignored'); const extRecommendations = this.extensionTipsService.getAllRecommendationsWithReason(); if (extRecommendations[extension.id.toLowerCase()]) { - removeClass(this.header, 'recommendation-ignored'); addClass(this.header, 'recommended'); this.recommendationText.textContent = extRecommendations[extension.id.toLowerCase()].reasonText; } From a7674d53536ba098107994d6ca7b145d230e6ef0 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 27 Jun 2018 17:06:06 -0700 Subject: [PATCH 172/228] Fix #53122 - don't focus the settings tree when navigating in the settings editor TOC --- .../parts/preferences/browser/settingsEditor2.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts b/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts index f973c023821..30adbcb795b 100644 --- a/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts @@ -272,18 +272,16 @@ export class SettingsEditor2 extends BaseEditor { twistiePixels: 15 }); - this._register(this.tocTree.onDidChangeSelection(e => { + this._register(this.tocTree.onDidChangeFocus(e => { + const element = e.focus; if (this.searchResultModel) { - const element = e.selection[0]; this.viewState.filterToCategory = element; this.refreshTreeAndMaintainFocus(); } else if (this.settingsTreeModel) { - const element = e.selection[0]; if (element && !e.payload.fromScroll) { this.settingsTree.reveal(element, 0); this.settingsTree.setSelection([element]); this.settingsTree.setFocus(element); - this.settingsTree.domFocus(); } } })); @@ -452,8 +450,8 @@ export class SettingsEditor2 extends BaseEditor { this.tocTree.reveal(element, 1); } - this.tocTree.setSelection([element], { fromScroll: true }); - this.tocTree.setFocus(element); + this.tocTree.setSelection([element]); + this.tocTree.setFocus(element, { fromScroll: true }); } } From bdd04868e87b959db8bc990623c61886dae76fdb Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 27 Jun 2018 18:00:28 -0700 Subject: [PATCH 173/228] Fix #53125 - focus settings list on 'enter' in TOC --- .../preferences/browser/settingsEditor2.ts | 13 ++++++++++++- .../parts/preferences/common/preferences.ts | 3 +++ .../preferences.contribution.ts | 19 ++++++++++++++++++- 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts b/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts index 30adbcb795b..20cc841055d 100644 --- a/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts @@ -33,7 +33,7 @@ import { SearchWidget, SettingsTarget, SettingsTargetsWidget } from 'vs/workbenc import { commonlyUsedData, tocData } from 'vs/workbench/parts/preferences/browser/settingsLayout'; import { ISettingsEditorViewState, NonExpandableTree, resolveExtensionsSettings, resolveSettingsTree, SearchResultIdx, SearchResultModel, SettingsAccessibilityProvider, SettingsDataSource, SettingsRenderer, SettingsTreeController, SettingsTreeElement, SettingsTreeFilter, SettingsTreeGroupElement, SettingsTreeModel, SettingsTreeSettingElement } from 'vs/workbench/parts/preferences/browser/settingsTree'; import { TOCDataSource, TOCRenderer, TOCTreeModel } from 'vs/workbench/parts/preferences/browser/tocTree'; -import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_FIRST_ROW_FOCUS, CONTEXT_SETTINGS_SEARCH_FOCUS, IPreferencesSearchService, ISearchProvider, CONTEXT_SETTINGS_ROW_FOCUS } from 'vs/workbench/parts/preferences/common/preferences'; +import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_FIRST_ROW_FOCUS, CONTEXT_SETTINGS_SEARCH_FOCUS, IPreferencesSearchService, ISearchProvider, CONTEXT_SETTINGS_ROW_FOCUS, CONTEXT_TOC_ROW_FOCUS } from 'vs/workbench/parts/preferences/common/preferences'; import { IPreferencesService, ISearchResult, ISettingsEditorModel } from 'vs/workbench/services/preferences/common/preferences'; import { SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput'; import { DefaultSettingsEditorModel } from 'vs/workbench/services/preferences/common/preferencesModels'; @@ -77,6 +77,7 @@ export class SettingsEditor2 extends BaseEditor { private firstRowFocused: IContextKey; private rowFocused: IContextKey; + private tocRowFocused: IContextKey; private inSettingsEditorContextKey: IContextKey; private searchFocusContextKey: IContextKey; @@ -103,6 +104,7 @@ export class SettingsEditor2 extends BaseEditor { this.searchFocusContextKey = CONTEXT_SETTINGS_SEARCH_FOCUS.bindTo(contextKeyService); this.firstRowFocused = CONTEXT_SETTINGS_FIRST_ROW_FOCUS.bindTo(contextKeyService); this.rowFocused = CONTEXT_SETTINGS_ROW_FOCUS.bindTo(contextKeyService); + this.tocRowFocused = CONTEXT_TOC_ROW_FOCUS.bindTo(contextKeyService); this._register(configurationService.onDidChangeConfiguration(e => { this.onConfigUpdate(); @@ -284,6 +286,15 @@ export class SettingsEditor2 extends BaseEditor { this.settingsTree.setFocus(element); } } + + })); + + this._register(this.tocTree.onDidFocus(() => { + this.tocRowFocused.set(true); + })); + + this._register(this.tocTree.onDidBlur(() => { + this.tocRowFocused.set(false); })); this.updateTOCVisible(); diff --git a/src/vs/workbench/parts/preferences/common/preferences.ts b/src/vs/workbench/parts/preferences/common/preferences.ts index ba8b28c4b26..232b6d0b8d4 100644 --- a/src/vs/workbench/parts/preferences/common/preferences.ts +++ b/src/vs/workbench/parts/preferences/common/preferences.ts @@ -62,6 +62,7 @@ export const CONTEXT_SETTINGS_EDITOR = new RawContextKey('inSettingsEdi export const CONTEXT_SETTINGS_SEARCH_FOCUS = new RawContextKey('inSettingsSearch', false); export const CONTEXT_SETTINGS_FIRST_ROW_FOCUS = new RawContextKey('firstSettingRowFocused', false); export const CONTEXT_SETTINGS_ROW_FOCUS = new RawContextKey('settingRowFocused', false); +export const CONTEXT_TOC_ROW_FOCUS = new RawContextKey('settingsTocRowFocus', false); export const CONTEXT_KEYBINDINGS_EDITOR = new RawContextKey('inKeybindings', false); export const CONTEXT_KEYBINDINGS_SEARCH_FOCUS = new RawContextKey('inKeybindingsSearch', false); export const CONTEXT_KEYBINDING_FOCUS = new RawContextKey('keybindingFocus', false); @@ -74,6 +75,8 @@ export const SETTINGS_EDITOR_COMMAND_FOCUS_FILE = 'settings.action.focusSettings export const SETTINGS_EDITOR_COMMAND_EDIT_FOCUSED_SETTING = 'settings.action.editFocusedSetting'; export const SETTINGS_EDITOR_COMMAND_FOCUS_SEARCH_FROM_SETTINGS = 'settings.action.focusSearchFromSettings'; export const SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_FROM_SEARCH = 'settings.action.focusSettingsFromSearch'; +export const SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_LIST = 'settings.action.focusSettingsList'; + export const KEYBINDINGS_EDITOR_COMMAND_SEARCH = 'keybindings.editor.searchKeybindings'; export const KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS = 'keybindings.editor.clearSearchResults'; export const KEYBINDINGS_EDITOR_COMMAND_DEFINE = 'keybindings.editor.defineKeybinding'; diff --git a/src/vs/workbench/parts/preferences/electron-browser/preferences.contribution.ts b/src/vs/workbench/parts/preferences/electron-browser/preferences.contribution.ts index a6452763887..683bcd616f2 100644 --- a/src/vs/workbench/parts/preferences/electron-browser/preferences.contribution.ts +++ b/src/vs/workbench/parts/preferences/electron-browser/preferences.contribution.ts @@ -22,7 +22,7 @@ import { KeybindingsEditor } from 'vs/workbench/parts/preferences/browser/keybin import { OpenRawDefaultSettingsAction, OpenSettingsAction, OpenGlobalSettingsAction, OpenGlobalKeybindingsFileAction, OpenWorkspaceSettingsAction, OpenFolderSettingsAction, ConfigureLanguageBasedSettingsAction, OPEN_FOLDER_SETTINGS_COMMAND, OpenGlobalKeybindingsAction, OpenSettings2Action } from 'vs/workbench/parts/preferences/browser/preferencesActions'; import { IKeybindingsEditor, IPreferencesSearchService, CONTEXT_KEYBINDING_FOCUS, CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS, KEYBINDINGS_EDITOR_COMMAND_DEFINE, KEYBINDINGS_EDITOR_COMMAND_REMOVE, KEYBINDINGS_EDITOR_COMMAND_SEARCH, - KEYBINDINGS_EDITOR_COMMAND_COPY, KEYBINDINGS_EDITOR_COMMAND_RESET, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND, KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR, KEYBINDINGS_EDITOR_COMMAND_FOCUS_KEYBINDINGS, KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_SEARCH, CONTEXT_SETTINGS_EDITOR, SETTINGS_EDITOR_COMMAND_FOCUS_FILE, CONTEXT_SETTINGS_SEARCH_FOCUS, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_FOCUS_NEXT_SETTING, SETTINGS_EDITOR_COMMAND_FOCUS_PREVIOUS_SETTING, SETTINGS_EDITOR_COMMAND_EDIT_FOCUSED_SETTING, SETTINGS_EDITOR_COMMAND_FOCUS_SEARCH_FROM_SETTINGS, SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_FROM_SEARCH, CONTEXT_SETTINGS_FIRST_ROW_FOCUS, CONTEXT_SETTINGS_ROW_FOCUS + KEYBINDINGS_EDITOR_COMMAND_COPY, KEYBINDINGS_EDITOR_COMMAND_RESET, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND, KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR, KEYBINDINGS_EDITOR_COMMAND_FOCUS_KEYBINDINGS, KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_SEARCH, CONTEXT_SETTINGS_EDITOR, SETTINGS_EDITOR_COMMAND_FOCUS_FILE, CONTEXT_SETTINGS_SEARCH_FOCUS, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_FOCUS_NEXT_SETTING, SETTINGS_EDITOR_COMMAND_FOCUS_PREVIOUS_SETTING, SETTINGS_EDITOR_COMMAND_EDIT_FOCUSED_SETTING, SETTINGS_EDITOR_COMMAND_FOCUS_SEARCH_FROM_SETTINGS, SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_FROM_SEARCH, CONTEXT_SETTINGS_FIRST_ROW_FOCUS, CONTEXT_SETTINGS_ROW_FOCUS, CONTEXT_TOC_ROW_FOCUS, SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_LIST } from 'vs/workbench/parts/preferences/common/preferences'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; @@ -476,3 +476,20 @@ const editFocusedSettingCommand2 = new EditFocusedSettingCommand2({ kbOpts: { primary: KeyCode.Enter } }); KeybindingsRegistry.registerCommandAndKeybindingRule(editFocusedSettingCommand2.toCommandAndKeybindingRule(KeybindingsRegistry.WEIGHT.workbenchContrib())); + +class FocusSettingsListCommand extends SettingsCommand { + + public runCommand(accessor: ServicesAccessor, args: any): void { + const preferencesEditor = this.getPreferencesEditor(accessor); + if (preferencesEditor instanceof SettingsEditor2) { + preferencesEditor.focusSettings(); + } + } +} + +const focusSettingsListCommand = new FocusSettingsListCommand({ + id: SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_LIST, + precondition: ContextKeyExpr.and(CONTEXT_SETTINGS_EDITOR, CONTEXT_TOC_ROW_FOCUS), + kbOpts: { primary: KeyCode.Enter } +}); +KeybindingsRegistry.registerCommandAndKeybindingRule(focusSettingsListCommand.toCommandAndKeybindingRule(KeybindingsRegistry.WEIGHT.workbenchContrib())); From fb7e3120c01634543e2432c9d65ee2e353548ba6 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 27 Jun 2018 18:08:46 -0700 Subject: [PATCH 174/228] Fix #53124 - clicking on empty space in settings editor take focus from text editor --- src/vs/workbench/parts/preferences/browser/settingsEditor2.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts b/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts index 20cc841055d..79fc29addb5 100644 --- a/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts @@ -116,6 +116,7 @@ export class SettingsEditor2 extends BaseEditor { } createEditor(parent: HTMLElement): void { + parent.setAttribute('tabindex', '-1'); this.rootElement = DOM.append(parent, $('.settings-editor')); this.createHeader(this.rootElement); From ad7253dacf4524e3cfcbb7395937eebfca0240ab Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 27 Jun 2018 18:12:12 -0700 Subject: [PATCH 175/228] Fix #53131 - "Text Editor" etc category label needs to match parent --- src/vs/workbench/parts/preferences/browser/settingsLayout.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/preferences/browser/settingsLayout.ts b/src/vs/workbench/parts/preferences/browser/settingsLayout.ts index 9acee728414..caf7d55ff89 100644 --- a/src/vs/workbench/parts/preferences/browser/settingsLayout.ts +++ b/src/vs/workbench/parts/preferences/browser/settingsLayout.ts @@ -70,7 +70,7 @@ export const tocData: ITOCEntry = { }, { id: 'editor/editor', - label: localize('editor', "Editor"), + label: localize('textEditor', "Text Editor"), settings: ['editor.*'] } ] From cf72b93a7d33675568526b20691b0955be74887f Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 27 Jun 2018 18:45:25 -0700 Subject: [PATCH 176/228] Settings editor - special-case "Commonly Used" settings height so it lines up with the top of the TOC --- .../preferences/browser/media/settingsEditor2.css | 4 ++++ .../parts/preferences/browser/settingsTree.ts | 11 +++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css index 5c553f38f89..e964523981f 100644 --- a/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css +++ b/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css @@ -285,6 +285,10 @@ font-size: 20px; } +.settings-editor > .settings-body > .settings-tree-container .settings-group-level-1.settings-group-first { + padding-top: 7px; +} + .settings-editor > .settings-body .settings-feedback-button { color: rgb(255, 255, 255); background-color: rgb(14, 99, 156); diff --git a/src/vs/workbench/parts/preferences/browser/settingsTree.ts b/src/vs/workbench/parts/preferences/browser/settingsTree.ts index 5d7a01d3f17..e36ea4f0a58 100644 --- a/src/vs/workbench/parts/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/parts/preferences/browser/settingsTree.ts @@ -70,6 +70,7 @@ export class SettingsTreeGroupElement extends SettingsTreeElement { children: (SettingsTreeGroupElement | SettingsTreeSettingElement)[]; label: string; level: number; + isFirstGroup: boolean; } export class SettingsTreeSettingElement extends SettingsTreeElement { @@ -111,6 +112,8 @@ export class SettingsTreeModel { update(newTocRoot = this._tocRoot): void { const newRoot = this.createSettingsTreeGroupElement(newTocRoot); + (newRoot.children[0]).isFirstGroup = true; + if (this._root) { this._root.children = newRoot.children; } else { @@ -451,6 +454,10 @@ export class SettingsRenderer implements IRenderer { getHeight(tree: ITree, element: SettingsTreeElement): number { if (element instanceof SettingsTreeGroupElement) { + if (element.isFirstGroup) { + return 31; + } + return 40 + (7 * element.level); } @@ -642,6 +649,10 @@ export class SettingsRenderer implements IRenderer { const labelElement = DOM.append(template.parent, $('div.settings-group-title-label')); labelElement.classList.add(`settings-group-level-${element.level}`); labelElement.textContent = (element).label; + + if (element.isFirstGroup) { + labelElement.classList.add('settings-group-first'); + } } private elementIsSelected(tree: ITree, element: SettingsTreeElement): boolean { From f200964ed5c42f66db21a12e5d8232480c0d9ae7 Mon Sep 17 00:00:00 2001 From: Ramya Achutha Rao Date: Wed, 27 Jun 2018 21:47:30 -0700 Subject: [PATCH 177/228] Fix Select next/item bug when cursor is at tag boundary --- extensions/emmet/src/selectItemHTML.ts | 4 ++-- .../src/test/editPointSelectItemBalance.test.ts | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/extensions/emmet/src/selectItemHTML.ts b/extensions/emmet/src/selectItemHTML.ts index d5dbbf33d05..7e90bb285e4 100644 --- a/extensions/emmet/src/selectItemHTML.ts +++ b/extensions/emmet/src/selectItemHTML.ts @@ -31,7 +31,7 @@ export function nextItemHTML(selectionStart: vscode.Position, selectionEnd: vsco // Get the first child of current node which is right after the cursor and is not a comment nextNode = currentNode.firstChild; - while (nextNode && (selectionEnd.isAfterOrEqual(nextNode.start) || nextNode.type === 'comment')) { + while (nextNode && (selectionEnd.isAfterOrEqual(nextNode.end) || nextNode.type === 'comment')) { nextNode = nextNode.nextSibling; } } @@ -63,7 +63,7 @@ export function prevItemHTML(selectionStart: vscode.Position, selectionEnd: vsco if (currentNode.type !== 'comment' && selectionStart.translate(0, -1).isAfter(currentNode.open.start)) { - if (selectionStart.isBefore(currentNode.open.end) || !currentNode.firstChild) { + if (selectionStart.isBefore(currentNode.open.end) || !currentNode.firstChild || selectionEnd.isBeforeOrEqual(currentNode.firstChild.start)) { prevNode = currentNode; } else { // Select the child that appears just before the cursor and is not a comment diff --git a/extensions/emmet/src/test/editPointSelectItemBalance.test.ts b/extensions/emmet/src/test/editPointSelectItemBalance.test.ts index a9f25685b91..f05469ab440 100644 --- a/extensions/emmet/src/test/editPointSelectItemBalance.test.ts +++ b/extensions/emmet/src/test/editPointSelectItemBalance.test.ts @@ -113,6 +113,22 @@ suite('Tests for Next/Previous Select/Edit point and Balance actions', () => { }); }); + test('Emmet Select Next/Prev item at boundary', function(): any { + return withRandomFileEditor(htmlContents, '.html', (editor, doc) => { + editor.selections = [new Selection(4, 1, 4, 1)]; + + fetchSelectItem('next'); + testSelection(editor.selection, 2, 4, 6); + + editor.selections = [new Selection(4, 1, 4, 1)]; + + fetchSelectItem('prev'); + testSelection(editor.selection, 1, 3, 5); + + return Promise.resolve(); + }); + }); + test('Emmet Next/Prev Item in html template', function (): any { const templateContents = `